From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: 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 6027E3858D28 for ; Tue, 10 Oct 2023 17:20:28 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 6027E3858D28 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1696958427; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=P2BAzwra1frgFf+LzcRcBegWq+WgX/6wgpK29wtTTIA=; b=dDOT2jgeKgMxCsqL5IrlDjTdC0/WaN5JvJWpdb88LkqNbu4FTai5Ld0eInuu9i56ScdlZV rjC/JQVmynu5rDege0X9xC6VDgNNJH3+z/uyoFNXGOyyr6cIC2es1ginArZI287ZgucEhy tL7xz1NWk45lehnbQjce9ZRKMmiCFLU= Received: from mail-qk1-f199.google.com (mail-qk1-f199.google.com [209.85.222.199]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-163-2v3jUlm3N8qCZosJTUmkYw-1; Tue, 10 Oct 2023 13:20:16 -0400 X-MC-Unique: 2v3jUlm3N8qCZosJTUmkYw-1 Received: by mail-qk1-f199.google.com with SMTP id af79cd13be357-77409676d7dso646467685a.1 for ; Tue, 10 Oct 2023 10:20:16 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1696958416; x=1697563216; h=user-agent:in-reply-to:content-disposition:mime-version:references :message-id:subject:cc:to:from:date:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=P2BAzwra1frgFf+LzcRcBegWq+WgX/6wgpK29wtTTIA=; b=Bg8Gya7UirCJbtAEutx74N3LdJRs1cA/6dSMcl5VY22EbD9FGifiVoBKNNYlazKnP0 +THbxqaw5tfJtXvIJm3wYmJqsmFArDKuq0wC4S78UODn5Nm3zpEKoFJWBGTEOIDPveAs vnfTzBUVpNlpLWIxtuPKLduS9jc1IRLJyj1/v3CNyHBgTIDnuD9sHlx+ChYOERHcdtEz EgyshYK3P5OEslEFPHsRQ9qSl9f/hn9Hwc2/lTXQLGlnVF1E/21MQuIrljZBd4SuiwYJ uAp2xmU4Q6Dgsef6L7YJswP7sYZMldqnGqQ1w54ZSwtI8rH61/NuZZFA4VWQ2LoQ0C+R 5EgQ== X-Gm-Message-State: AOJu0Yx3JVUpZIePaiGztrjMewC1w+SX5XEKYTtzy6WSz81DDtc6Kzsz c36AlWE9PHO6NAJNQcq0OAFo4cK3AWPFSa9nvn6cESJKtBIdwgdnvzVvrB4TavZSi82T/ZzMVzJ sTD2uSPURbox2329PgDEM+XiE7Q== X-Received: by 2002:a05:620a:2590:b0:777:2d53:ea69 with SMTP id x16-20020a05620a259000b007772d53ea69mr1772165qko.31.1696958413326; Tue, 10 Oct 2023 10:20:13 -0700 (PDT) X-Google-Smtp-Source: AGHT+IFU0SDeZAlpcfIHxI99UVZsqFWHrSeFm4QqCXjGRgpOcQjA4iKRHBe0u6NjP4dzgd5RgTJ3dw== X-Received: by 2002:a05:620a:2590:b0:777:2d53:ea69 with SMTP id x16-20020a05620a259000b007772d53ea69mr1772081qko.31.1696958411476; Tue, 10 Oct 2023 10:20:11 -0700 (PDT) Received: from redhat.com (2603-7000-9500-34a5-0000-0000-0000-1db4.res6.spectrum.com. [2603:7000:9500:34a5::1db4]) by smtp.gmail.com with ESMTPSA id vr3-20020a05620a55a300b007756fe0bb17sm4486318qkn.19.2023.10.10.10.20.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Oct 2023 10:20:10 -0700 (PDT) Date: Tue, 10 Oct 2023 13:20:09 -0400 From: Marek Polacek To: Jason Merrill Cc: GCC Patches Subject: [PATCH v2] c++: implement P2564, consteval needs to propagate up [PR107687] Message-ID: References: <20230823194904.1925591-1-polacek@redhat.com> <974dbde5-e1d8-90dc-a023-df214883403c@redhat.com> MIME-Version: 1.0 In-Reply-To: <974dbde5-e1d8-90dc-a023-df214883403c@redhat.com> User-Agent: Mutt/2.2.9 (2022-11-12) X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=us-ascii Content-Disposition: inline X-Spam-Status: No, score=-11.6 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,KAM_SHORT,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H4,RCVD_IN_MSPIKE_WL,RCVD_IN_SORBS_WEB,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 List-Id: On Tue, Aug 29, 2023 at 03:26:46PM -0400, Jason Merrill wrote: > On 8/23/23 15:49, Marek Polacek wrote: > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? > > > > -- >8 -- > > > > This patch implements P2564, described at , whereby > > certain functions are promoted to consteval. For example: > > Great, thanks! Thanks for looking into this. It's kept me occupied for quite a while. > > consteval int id(int i) { return i; } > > > > template > > constexpr int f(T t) > > { > > return t + id(t); // id causes f to be promoted to consteval > > } > > > > void g(int i) > > { > > f (3); > > } > > > > now compiles. Previously the code was ill-formed: we would complain > > that 't' in 'f' is not a constant expression. Since 'f' is now > > consteval, it means that the call to id(t) is in an immediate context, > > so doesn't have to produce a constant -- this is how we allow consteval > > functions composition. But making 'f' consteval also means that > > the call to 'f' in 'g' must yield a constant; failure to do so results > > in an error. I made the effort to have cc1plus explain to us what's > > going on. For example, calling f(i) produces this neat diagnostic: > > > > q.C: In function 'void g(int)': > > q.C:11:5: error: call to consteval function 'f(i)' is not a constant expression > > 11 | f (i); > > | ~~^~~ > > q.C:6:16: note: 'constexpr int f(T) [with T = int]' was promoted to an immediate function because its body contains an immediate-escalating expression 'id(t)' > > 6 | return t + id(t); > > | ~~^~~ > > > > which hopefully makes it clear what's going on. > > > > Implementing this proposal has been tricky. One problem was delayed > > instantiation: instantiating a function can set off a domino effect > > where one call promotes a function to consteval but that then means > > that another function should also be promoted, etc. > > What I expected was that we would instantiate when we see a call, i.e. > immediate_invocation_p would instantiate its argument if it's an > immediate-escalating function. But... > > > I previously thought that I could get past that by implementing the propagation in > > cp_gimplify_expr at which point we have already instantiated everything > > via instantiate_pending_templates. > > ...this sounds like a clever way to avoid the problems we tend to see with > eager instantiation (e.g. constexpr-inst1.C). It still seems promising to > me. This v2 does the propagation much later. > > But I realized that we don't gimplify e.g. > > > > static auto p = &id; > > Indeed, just cp_fully_fold_init them, at least until we build the global > init function. This is now handled differently, as described below. > > and so we'd never detect taking the address of a consteval function. > > Therefore this patch instantiates immediate-escalating functions > > beforehand. > > Relatedly, in one of the testcases you have > > > +template > > +constexpr int f2(T); > > + > > +// ??? clang++ emits > > +// error: immediate function 'f2' used before it is defined > > +// error: immediate function 'f2' used before it is defined > > +// but at this point we don't know that f2 will be updated to consteval? > > +auto a2 = &f2; > > +auto b2 = &f2; > > + > > +template > > +constexpr int f2(T t) { > > + return id(t); > > This is a case where we can't immediately resolve the question anyway, we > need to remember where we've seen bare references to immediate-escalating > functions outside of other immediate-escalating (or already immediate) > functions, and complain later if they became consteval when instantiated. This now works correctly by remembering the references until EOF. > I don't see why this is a significant obstacle to doing escalation late. I've described some of this in the patch description. > > * cp-tree.h (ADDR_EXPR_DENOTES_CALL_P): Define. > > Is this flag just for rejecting consteval-prop10.C? I think we should not > try to reject it. This came up previously in the context of my patch for > CWG2631, at which point I argued to CWG that it should be allowed, but > nobody responded. I'll ping that thread now, but in the mean time let's not > go out of our way to reject this testcase that seems to me pretty clearly > allowed by the current wording: that expression invokes an immediate > function, so it's an immediate invocation. (This flag was introduced in my earlier patch.) > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc > > index 673ec91d60e..31f78b71fe1 100644 > > --- a/gcc/cp/call.cc > > +++ b/gcc/cp/call.cc > > @@ -9735,7 +9735,9 @@ build_trivial_dtor_call (tree instance, bool no_ptr_deref) > > /* Return true if in an immediate function context, or an unevaluated operand, > > or a default argument/member initializer, or a subexpression of an immediate > > - invocation. */ > > + invocation. > > + ??? P2564 says that "a subexpression of a manifestly constant-evaluated > > + expression or conversion" is also an immediate function context. */ > > Yes, that was to allow the consteval-prop2.C > > static_assert(is_not(5, is_even)); > > Since the operand of static_assert is manifestly constant-evaluated, it's OK > that we name consteval is_even there. > > We currently use this function to trigger evaluating immediate evaluations, > and later (through immediate_invocation_p) to trigger errors, and other > constant-evaluation happens between those two, so in most cases it's OK that > this function does not identify that situation. > > But there's another example in the paper, that this patch doesn't handle: > > consteval int g(int p) { return p; } > template constexpr auto f(T) { return g; } > int r = f(1)(2); // proposed ok > int s = f(1)(2) + r; // error > > Here f(1) looks like an immediate invocation but is not itself constant, and > GCC with this patch tries to immediately evaluate f(1) and gives an error. > But since it's in a manifestly constant-evaluated expression, it isn't > actually an immediate invocation, and giving an error is wrong. This test is now handled correctly (consteval-prop19.C). > Maybe we want to stop doing immediate evaluation in build_over_call (and > bot_replace) at all, and instead build the call as normal and perhaps error > later in cp_fold_r? If I just disable the immediate_invocation_p blocks in > build_over_call the testcase above works as the comments indicate. Maybe > removing the bot_replace code would also help with 110997? This has now been resolved in an earlier patch. > > +/* Return true if FN is an immediate-escalating function. */ > > + > > +bool > > +immediate_escalating_function_p (tree fn) > > +{ > > + if (!fn) > > + return false; > > + > > + gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL); > > I think we might want to have a flag for this, like I did with > -fnew-ttp-matching. Say, -fimmediate-escalation? Added in v2. > > + /* [expr.const]p16 "An expression or conversion is > > + immediate-escalating if it is not initially in an immediate > > + function context and it is either > > + -- an immediate invocation that is not a constant expression and > > + is not a subexpression of an immediate invocation." > > + > > + If we are in an immediate-escalating function, the > > + immediate-escalating expression or conversion makes it an > > + immediate function. So CALL does not need to produce a constant > > + expression. ??? It's ugly to call cxx_constant_value twice in > > + some cases. */ > > + if (cxx_constant_value (call, obj_arg, tf_none) == error_mark_node > > + && maybe_promote_function_to_consteval (current_function_decl)) > > + return orig_call; > > call = cxx_constant_value (call, obj_arg, complain); > > As discussed above, I think promoting or complaining here is now wrong > because we might be in a manifestly constant-evaluated expression. At this > point I think there's probably no point even trying to do evaluation here, > we can just wait until cp_fold_r (or the later escalation time?). This code is gone now. > > diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc > > index 206e791fcfd..902c9d54741 100644 > > --- a/gcc/cp/cp-gimplify.cc > > +++ b/gcc/cp/cp-gimplify.cc > > > +/* Figure out if DECL should be promoted to consteval and if so, > maybe also > > + promote the function we are in currently. CALL is the CALL_EXPR of DECL, > > + or NULL_TREE, if we're taking the address of a function. This function > > + will likely instantiate DECL. */ > > + > > +static void > > +maybe_escalate_decl_and_cfun (tree decl, tree call = NULL_TREE) > > +{ > > + if (cxx_dialect <= cxx17 || cp_unevaluated_operand) > > + return; > > Return early if cfun is not immediate-escalating? I think I can't, we may have to promote c_f_d. > > + /* Compiler-generated functions don't seem like good candidates for > > + promoting. */ > > + if (DECL_ARTIFICIAL (decl) || DECL_ABSTRACT_P (decl)) > > Why DECL_ABSTRACT_P? I think that will be set on pre-cloning constructors. Ah, I threw it in just because. Now removed. > > + return; > > + > > + /* What we're calling is not a consteval function but it may become > > + one. This requires recursing; DECL may be promoted to consteval > > + because it contains an escalating expression E, but E itself may > > + have to be promoted first, etc. */ > > + if (!DECL_IMMEDIATE_FUNCTION_P (decl) > > + && immediate_escalating_function_p (decl)) > > + { > > + /* PSET holds the set of functions we have already perused for > > + immediate-escalating expressions. */ > > + static hash_set pset; > > It will also hold pointers to all the function-internal expressions that we > ever walk through, which seems like a problem. IMO it would be better to > have a persistent pset that just tracks the functions, in addition to the > usual tree-walk pset that gets thrown away each time. That is, if we need to > do this walk at all; see the next comment. The whole hash_set is gone; I'm using DECL_ESCALATED_P which I needed for maybe_explain_promoted_consteval as well. > > + find_escalating_expr_t data = { decl, &pset }; > > + /* We can't delay instantiating any further. We need to see the > > + whole tree to decide whether DECL is consteval. > > + ??? Consider adding a sentinel to instantiate_constexpr_fns so > > + that we don't escalate while we instantiate while we escalate... > > + which seems dodgy. > > It doesn't seem that dodgy to me. But if we're doing escalation at > finish_function time rather than trying to defer to EOF we shouldn't need to > do this tree walk at all, only instantiation, and the instantiation will > deal with its own escalation (or not). And if we do end up deferring to > EOF, we shouldn't need to do any instantiation because as you mentioned in > the intro we will have already done instantiate_pending_templates. v2 doesn't do any extra instantiation anymore. > > + FIXME Instantiating a defaulted ctor breaks modules (ICE due to > > + cp_function_chain being null). */ > > Sounds like this needs more investigation. No longer a problem. > Incidentally, defaulted functions aren't instantiated, they're synthesized. > Which instantiate_constexpr_fns deals with, though it seems a bit odd to do > a "walk" over a single node, maybe we should factor that bit out? I'm not using instantiate_constexpr_fns in v2 anymore so I didn't touch it. > > + /* In turn, maybe promote the function we find ourselves in... */ > > + if (DECL_IMMEDIATE_FUNCTION_P (decl) > > + /* ...but not if the call to DECL was constant; that is the > > + "an immediate invocation that is not a constant expression" > > + case. We do this here and not in find_escalating_expr_r, > > + because DECL could have already been consteval and we'd > > + never call f_e_e_r. */ > > It seems unfortunate to call cxx_constant_value here and then again > immediately in cp_fold_r in the case that call is indeed an immediate > invocation. True. This was easy to fix though -- maybe_escalate_decl_and_cfun now saves the evaluated tree in *evalp. > > + && (!call || cxx_constant_value (call, tf_none) == error_mark_node)) > > + maybe_promote_function_to_consteval (current_function_decl); > > +} > > + > > @@ -1046,27 +1204,64 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_) > > switch (code) > > { > > case PTRMEM_CST: > > - if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL > > - && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt))) > > + if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL) > > { > > - if (!data->pset.add (stmt)) > > - error_at (PTRMEM_CST_LOCATION (stmt), > > - "taking address of an immediate function %qD", > > - PTRMEM_CST_MEMBER (stmt)); > > - stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt)); > > - break; > > + tree decl = PTRMEM_CST_MEMBER (stmt); > > + maybe_escalate_decl_and_cfun (decl); > > + /* Taking the address of a consteval function is not permitted. */ > > + if (immediate_invocation_p (decl)) > > + { > > + if (!data->pset.add (stmt)) > > + { > > + error_at (PTRMEM_CST_LOCATION (stmt), > > + "taking address of an immediate function %qD", > > + decl); > > + maybe_explain_promoted_consteval (PTRMEM_CST_LOCATION (stmt), > > + decl); > > + } > > + stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt)); > > + } > > Can we share most of this code between PTRMEM_CST and ADDR_EXPR? Yes, done. > > + case CALL_EXPR: > > + if (tree fn = CALL_EXPR_FN (stmt)) > > + if (TREE_CODE (fn) == ADDR_EXPR > > + && ADDR_EXPR_DENOTES_CALL_P (fn) > > + && TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL) > > + { > > + tree decl = TREE_OPERAND (fn, 0); > > + maybe_escalate_decl_and_cfun (decl, stmt); > > + if (immediate_invocation_p (decl)) > > + { > > + tree e = cxx_constant_value (stmt, tf_none); > > + if (e == error_mark_node) > > + { > > + const location_t loc = cp_expr_loc_or_input_loc (stmt); > > + error_at (loc, "call to consteval function %qE is not a " > > + "constant expression", stmt); > > + maybe_explain_promoted_consteval (loc, decl); > > I think here we also want the usual cxx_constant_value errors explaining why > the call is not constant. Added. > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h > > index eb901683b6d..36d76a98233 100644 > > --- a/gcc/cp/cp-tree.h > > +++ b/gcc/cp/cp-tree.h > > @@ -4784,6 +4784,11 @@ get_vec_init_expr (tree t) > > #define PTRMEM_OK_P(NODE) \ > > TREE_LANG_FLAG_0 (TREE_CHECK3 ((NODE), ADDR_EXPR, OFFSET_REF, SCOPE_REF)) > > +/* True if this ADDR_EXPR denotes a function call; that is, it's > > + fn() rather than &fn. */ > > +#define ADDR_EXPR_DENOTES_CALL_P(NODE) \ > > + (ADDR_EXPR_CHECK(NODE)->base.protected_flag) > > As mentioned above, I don't think we need this. Resolved earlier. > > diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc > > index d5c0c85ed51..03b642fefae 100644 > > --- a/gcc/cp/typeck.cc > > +++ b/gcc/cp/typeck.cc > > @@ -7248,7 +7248,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain) > > set for possible later diagnostics. */ > > if (TREE_CODE (val) == ADDR_EXPR > > && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL > > - && DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (val, 0))) > > + && (DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (val, 0)) > > + /* A constexpr function may be promoted to consteval. */ > > + || DECL_DECLARED_CONSTEXPR_P (TREE_OPERAND (val, 0)))) > > Hmm, why don't we just do this for all functions? Unclear. I've dropped the DECL_IMMEDIATE_FUNCTION_P check. > > SET_EXPR_LOCATION (val, input_location); > > return val; > > diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C > > index 3ce513d6e25..7b3e2db6c8a 100644 > > --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C > > +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C > > @@ -1,6 +1,9 @@ > > +// This used to... > > // Test that we don't uselessly instantiate f immediately while parsing g. > > // Doing so is permitted by the standard, but has no benefit and breaks code > > // unnecessarily. > > +// ...but due to P2564 we actually do instantiate f, because we need to figure > > +// out if it should be promoted to consteval. :-( > > This is an example that would work better if we can wait until EOF to do > escalation. This test no longer needs any modification. > > diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C > > new file mode 100644 > > index 00000000000..2ff16b88aa1 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C > > @@ -0,0 +1,149 @@ > > +// P2564R3 > > +// { dg-do compile { target c++20 } } > > +// Some of these were cribbed from clang's cxx2b-consteval-propagate.cpp. > > + > > +consteval int id(int i) { return i; } > > + > > +template > > +constexpr int > > +f4 (T t) > > +{ > > + auto p = id; > > + (void) p; > > + return t; > > +} > > + > > +// ??? We promote f4 to consteval but clang++ doesn't seem to. > > +auto p6 = &f4; // { dg-error "taking address of an immediate function" } > > I imagine clang doesn't promote because it optimizes away the unused > reference to id. That seems like a clang bug. OK. > > +static_assert (f4 (42) == 42); > > + > > +// Constructors. > > +consteval int zero (int) > > +{ > > + return 0; > > +} > > + > > +struct A { > > + // A::A(auto) promoted to consteval. > > + constexpr A(auto i) { zero (i); } > > +}; > > + > > +constexpr void > > +f5 (auto i) > > +{ > > + A a{i}; > > +} > > Maybe also a non-template function that gets an error? Added. > > +struct C { > > + // C::C(int) promoted to consteval. > > + consteval C (int) {}; > > Looks explicitly declared consteval, not promoted. :) Oop, comment removed. > > +struct Y { > > + int y; > > + int x = id (y); > > + consteval Y (int i) : y (id (i)) {} > > +}; > > + > > +Y y1(1); > > +Y y2(g); // { dg-error "not usable" } > > How about a variant of this where Y(int) is promoted to consteval? Added. > > +auto l1 = [](int i) constexpr { > > + int t = id (i); > > + return id (0); > > +}; > > + > > +int (*p)(int) = l1; // { dg-error "returns address of immediate function" } > > What if the lambda isn't explicitly constexpr? Added. The result is the same, and clang++ agrees. > > diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C > > new file mode 100644 > > index 00000000000..00af77042cb > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C > > @@ -0,0 +1,9 @@ > > +// P2564R3 > > +// { dg-do compile { target c++20 } } > > + > > +consteval int foo () { return 42; } > > +// Even though the standard doesn't define "invocation", this is one. > > +// But the user actually wrote '&' so presumably we should error. If > > +// we decide to accept this, move the ADDR_EXPR_DENOTES_CALL_P setting > > +// to build_cxx_call. > > +int bar () { return (*(&foo)) (); } // { dg-error "taking address" } > > As above, I think we can/should accept this, so I don't see the purpose of > ADDR_EXPR_DENOTES_CALL_P. Resolved earlier. > > diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C > > new file mode 100644 > > index 00000000000..f52ba07ec6c > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C > > @@ -0,0 +1,81 @@ > > +// P2564R3 > > +// { dg-do compile { target c++20 } } > > +// { dg-options "-Wno-c++23-extensions" } > > + > > +consteval int id (int i) { return i; } > ... > > +constexpr int > > +f6 (auto) > > +{ > > + // This call is a constant expression, so don't promote f6. > > + return f4 (42); > > +} > > + > > +constexpr int > > +f7 (auto i) > > +{ > > + if consteval { > > + auto p = &id; > > + (void) p; > > What if we return id(i) in the if consteval? Test added. In that case we are not supposed to promote the function. > > + } > > + return i; > > +} > > + > > +constexpr int > > +f8 (auto i) > > +{ > > + if not consteval { > > + (void) 0; > > + } else { > > + auto p = &id; > > + (void) p; > > And here. Also added, and we also handle this as expected. > > diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C > > new file mode 100644 > > index 00000000000..846e5603fee > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C > > @@ -0,0 +1,32 @@ > > +// P2564R3 > > +// { dg-do compile { target c++20 } } > > +// Test default arguments. > > + > > +consteval int id (int i) { return i; } > > + > > +template > > +constexpr int > > +f1 (int i = id (42)) > > +{ > > + return i; > > +} > > + > > +int non_const; > > + > > +template > > +constexpr int > > +f2 (int i = id (non_const)) > > +{ > > + return i; > > +} > > + > > +void > > +g (int i) > > +{ > > + f1 (42); > > + f1 (i); > > + f1 (); > > + f2 (42); > > + f2 (i); > > + f2 (); // { dg-error "not usable" } > > +} > > Also test that a function f3(auto) that calls f2() gets promoted? Done. > > diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C > > new file mode 100644 > > index 00000000000..a5803736d37 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C > > @@ -0,0 +1,54 @@ > > +// P2564R3 > > +// { dg-do compile { target c++20 } } > > +// Testcase from P2564R3. > > + > > +consteval int id(int i) { return i; } > > +constexpr char id(char c) { return c; } > > + > > +template > > +constexpr int f(T t) { > > + return t + id(t); > > +} > > + > > +auto a = &f; // OK, f is not an immediate function > > +auto b = &f; // { dg-error "taking address of an immediate function" } > > + > > +static_assert(f(3) == 6); // OK > > + > > +template > > +constexpr int g(T t) { // g is not an immediate function > > + return t + id(42); // because id(42) is already a constant > > +} > > I don't see any uses of g to test the comment? Added. > > +template > > +constexpr bool is_not(T t, F f) { > > + return not f(t); > > +} > > + > > +consteval bool is_even(int i) { return i % 2 == 0; } > > + > > +static_assert(is_not(5, is_even)); // OK > > + > > +int x = 0; > > + > > +template > > +constexpr T h(T t = id(x)) { // h is not an immediate function > > + return t; > > +} > > + > > +template > > +constexpr T hh() { // hh is an immediate function > > + return h(); // { dg-error "not usable in a constant expression" } > > Why is there an error on this line? So now we say here error: the value of 'x' is not usable in a constant expression > > +} > > + > > +int i = hh(); // error: hh() is an immediate-escalating expression > > + // outside of an immediate-escalating function > > ...and not on this line? And here we now say: error: call to consteval function 'hh()' is not a constant expression error: 'constexpr T hh() [with T = int]' called in a constant expression > > +struct A { > > + int x; > > + int y = id(x); > > +}; > > + > > +template > > +constexpr int k(int) { // k is not an immediate function because A(42) is a > > + return A(42).y; // constant expression and thus not immediate-escalating > > +} > > Needs use(s) of k to test the comment. True, and that revealed what I think is a bug in the standard. In the test I'm saying: // ??? [expr.const]#example-9 says: // k is not an immediate function because A(42) is a // constant expression and thus not immediate-escalating // But I think the call to id(x) is *not* a constant expression and thus // it is an immediate-escalating expression. Therefore k *is* // an immediate function. So we get the error below. clang++ agrees. id(x) is not a constant expression because x isn't constant. > > diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C > > new file mode 100644 > > index 00000000000..35665304652 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C > > @@ -0,0 +1,31 @@ > > +// P2564R3 > > +// { dg-do compile { target c++20 } } > > +// Cribbed from clang's cxx2b-consteval-propagate.cpp. > > + > > +consteval int id(int i) { return i; } > > + > > +template > > +constexpr int f(T t); > > + > > +auto a1 = &f; > > +auto b1 = &f; > > + > > +template > > +constexpr int f(T t) { > > + return id(0); > > +} > > + > > +template > > +constexpr int f2(T); > > + > > +// ??? clang++ emits > > +// error: immediate function 'f2' used before it is defined > > +// error: immediate function 'f2' used before it is defined > > +// but at this point we don't know that f2 will be updated to consteval? > > Right, as mentioned above it seems that we need to keep track of forward > references to immediate-escalating functions in case they become consteval. > If we're promoting early, only ones that aren't defined yet. This seems to work correctly now. > > +auto a2 = &f2; > > +auto b2 = &f2; > > + > > +template > > +constexpr int f2(T t) { > > + return id(t); > > +} > > diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C > > new file mode 100644 > > index 00000000000..8cc08c7f6d8 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C > > @@ -0,0 +1,58 @@ > > +// P2564R3 > > +// { dg-do compile { target c++20 } } > > +// From cxx2b-consteval-propagate.cpp. > > + > > +void side_effect(); > > + > > +consteval int > > +f (int x) > > +{ > > + if (!x) > > + side_effect(); // { dg-error "call to non-.constexpr. function" } > > + return x; > > +} > > + > > +struct SS { > > + int y = f(1); > > + int x = f(0); > > + SS(); > > +}; > > +SS::SS(){} > > Maybe a dg-message here, assuming the context for the above error mentions > this line? Now we say here: error: call to consteval function 'f(0)' is not a constant expression > > +consteval int > > +f2 (int x) > > +{ > > + if (!__builtin_is_constant_evaluated ()) > > + side_effect(); > > + return x; > > +} > > + > > +struct S2 { > > + int x = f2(0); > > + constexpr S2(); > > +}; > > + > > +constexpr S2::S2(){} > > +S2 s = {}; > > +constinit S2 s2 = {}; > > + > > +struct S3 { > > + int x = f2(0); > > + S3(); > > +}; > > +S3::S3(){} > > + > > +consteval int undef (int x); // { dg-warning "never defined" } > > + > > +struct X { > > + int a = sizeof(undef(0)); > > + int x = undef(0); > > No diagnostic on this line? No :(. We don't show that line even when there's no propagation taking place. > > + > > + X() = default; // { dg-error "used before its definition" } > > +}; > > + > > +void > > +test () > > +{ > > + [[maybe_unused]] X x; > > +} > > diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C > > new file mode 100644 > > index 00000000000..41c47992ef7 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C > > @@ -0,0 +1,71 @@ > > +// P2564R3 > > +// { dg-do compile { target c++20 } } > > +// { dg-options "-Wno-c++23-extensions" } > > + > > +consteval int zero (int) > > +{ > > + return 0; > > +} > > + > > +struct A { > > + // A::A(auto) promoted to consteval. > > + constexpr A(auto i) { zero (i); } > > +}; > > + > > +// 'f1' is an immediate function because its body contains a call to an > > +// immediate constructor 'A' and that call is not a constant expression > > +constexpr void > > +f1 (auto i) > > +{ > > + A a{i}; > > +} > > + > > +// 'f2' is an immediate function because its body contains a call to an > > +// immediate constructor 'A' and that call is not a constant expression > > +constexpr void > > +f2 (auto i) > > +{ > > + A a{i}; > > +} > > + > > +// ??? This doesn't give error when inline/constexpr and not called. > > Is that still true with the current patch? If so, it should be fixed. In > any case, it should be tested. This now works correctly, tests added. > > +void > > +f3 (int i) > > +{ > > + A a{i}; // { dg-error "not a constant expression" } > > +} > > diff --git a/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc b/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc > > index 16e94864f3b..329f3a6cc33 100644 > > --- a/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc > > +++ b/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc > > @@ -8,11 +8,11 @@ test01() > > { > > std::optional o; > > o.or_else([&] { return o; }); // OK > > - o.or_else([] { return std::optional(); }); // { dg-error "here" } > > - o.or_else([] { return 1; }); // { dg-error "here" } > > - std::move(o).or_else([] { return std::optional(); }); // { dg-error "here" } > > - std::move(o).or_else([] { return 1; }); // { dg-error "here" } > > -} > > + o.or_else([] { return std::optional(); }); > > + o.or_else([] { return 1; }); > > + std::move(o).or_else([] { return std::optional(); }); > > + std::move(o).or_else([] { return 1; }); > > +} // { dg-error "here" } > > This seems like a significant regression in source location quality. > Likewise for several other libstdc++ tests. These are all gone in v2. I'm ecstatic. So. I think we want to refrain from instantiating things early given how many problems that caused. On the other hand, stashing all the immediate-escalating decls into immediate_escalating_decls and walking their bodies isn't going to be cheap. I've checked how big the vectors can get, but our testsuite isn't the best litmus test because it's mostly smallish testcases without many #includes. The worst offender is uninit-pr105562.C with (gdb) p immediate_escalating_decls->length() $2 = 2204 (gdb) p deferred_escalating_exprs->length() $3 = 501 Compiling uninit-pr105562.C with g++13 and g++14 with this patch: real 7.51 real 7.67 user 7.32 user 7.49 sys 0.15 sys 0.14 I've made sure not to walk the same bodies twice. But there's room for further optimization; I suppose we could escalate instantiated functions right away rather than putting them into immediate_escalating_decls and waiting till later. I'm not certain if I can just look at DECL_TEMPLATE_INSTANTIATED. I suppose some functions cannot possibly be promoted because they don't contain any CALL_EXPRs. So we may be able to rule them out while doing cp_fold_r early. If a function is trivial, can it ever be promoted? And so on. Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? -- >8 -- This patch implements P2564, described at , whereby certain functions are promoted to consteval. For example: consteval int id(int i) { return i; } template constexpr int f(T t) { return t + id(t); // id causes f to be promoted to consteval } void g(int i) { f (3); } now compiles. Previously the code was ill-formed: we would complain that 't' in 'f' is not a constant expression. Since 'f' is now consteval, it means that the call to id(t) is in an immediate context, so doesn't have to produce a constant -- this is how we allow consteval functions composition. But making 'f' consteval also means that the call to 'f' in 'g' must yield a constant; failure to do so results in an error. I made the effort to have cc1plus explain to us what's going on. For example, calling f(i) produces this neat diagnostic: w.C:11:11: error: call to consteval function 'f(i)' is not a constant expression 11 | f (i); | ~~^~~ w.C:11:11: error: 'i' is not a constant expression w.C:6:22: note: 'constexpr int f(T) [with T = int]' was promoted to an immediate function because its body contains an immediate-escalating expression 'id(t)' 6 | return t + id(t); // id causes f to be promoted to consteval | ~~^~~ which hopefully makes it clear what's going on. Implementing this proposal has been tricky. One problem was delayed instantiation: instantiating a function can set off a domino effect where one call promotes a function to consteval but that then means that another function should also be promoted, etc. In v1, I addressed the delayed instantiation problem by instantiating trees early, so that we can escalate functions right away. That caused a number of problems, and in certain cases, like consteval-prop3.C, it can't work, because we need to wait till EOF to see the definition of the function anyway. Overeager instantiation tends to cause diagnostic problems too. In v2, I attempted to move the escalation to the gimplifier, at which point all templates have been instantiated. That attempt flopped, however, because once we've gimplified a function, its body is discarded and as a consequence, you can no longer evaluate a call to that function which is required for escalating, which needs to decide if a call is a constant expression or not. Therefore, we have to perform the escalation before gimplifying, but after instantiate_pending_templates. That's not easy because we have no way to walk all the trees. In the v2 patch, I use two vectors: one to store function decls that may become consteval, and another to remember references to immediate-escalating functions. Unfortunately the latter must also stash functions that call immediate-escalating functions. Consider: int g(int i) { f(i); // f is immediate-escalating } where g itself is not immediate-escalating, but we have to make sure that if f gets promoted to consteval, we give an error. A new option, -fno-immediate-escalation, is provided to suppress escalating functions. v2 also adds a new flag, DECL_ESCALATED_P, so that we don't escalate a function multiple times, and so that we can distinguish between explicitly consteval functions and functions that have been promoted to consteval. PR c++/107687 PR c++/110997 gcc/c-family/ChangeLog: * c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval. * c.opt (fimmediate-escalation): New option. gcc/cp/ChangeLog: * call.cc (in_immediate_context): No longer static. * constexpr.cc (cxx_eval_conditional_expression): Adjust the call to cp_fold_immediate. * cp-gimplify.cc (immediate_escalating_decls, deferred_escalating_exprs): New vecs. (enum fold_flags): Add ff_escalating. (immediate_escalating_function_p): New. (maybe_promote_function_to_consteval): New. (maybe_store_cfun_for_late_checking): New. (find_escalating_expr_r): New. (maybe_explain_promoted_consteval): New. (maybe_escalate_decl_and_cfun): New. (cp_gimplify_expr) : Assert we've handled all immediate invocations. (taking_address_of_imm_fn_error): New. (cp_fold_immediate_r): Merge ADDR_EXPR and PTRMEM_CST cases. Implement P2564 - promoting functions to consteval. : Implement P2564 - promoting functions to consteval. (cp_fold_immediate): New bool parameter, use it. Return true if we emitted errors. (maybe_store_immediate_escalating_fn): New. (process_pending_immediate_escalating_fns): New. (check_immediate_escalating_refs): New. * cp-tree.h (struct lang_decl_fn): Add escalated_p bit-field. (DECL_ESCALATED_P): New. (immediate_invocation_p): Declare. (cp_fold_immediate): Adjust declaration. (check_immediate_escalating_refs): Declare. (maybe_store_immediate_escalating_fn): Declare. (process_pending_immediate_escalating_fns): Declare. * decl.cc (finish_function): Call maybe_store_immediate_escalating_fn. * decl2.cc (c_parse_final_cleanups): Call process_pending_immediate_escalating_fns and check_immediate_escalating_refs. * module.cc (trees_out::lang_decl_bools): New comment. (trees_in::lang_decl_bools): New comment. * typeck.cc (cp_build_addr_expr_1): Don't check DECL_IMMEDIATE_FUNCTION_P. gcc/ChangeLog: * doc/invoke.texi: Document -fno-immediate-escalation. libstdc++-v3/ChangeLog: * testsuite/18_support/comparisons/categories/zero_neg.cc: Add dg-prune-output. * testsuite/std/format/string_neg.cc: Add dg-error. gcc/testsuite/ChangeLog: * g++.dg/cpp23/consteval-if10.C: Remove dg-error. * g++.dg/cpp23/consteval-if2.C: Likewise. * g++.dg/cpp23/feat-cxx2b.C: Adjust expected value of __cpp_consteval. * g++.dg/cpp26/feat-cxx26.C: Likewise. * g++.dg/cpp2a/consteval-memfn1.C: Add dg-error. * g++.dg/cpp2a/consteval11.C: Likewise. * g++.dg/cpp2a/consteval3.C: Adjust dg-error. * g++.dg/cpp2a/consteval34.C: Add dg-error. * g++.dg/cpp2a/consteval9.C: Likewise. * g++.dg/cpp2a/feat-cxx2a.C: Adjust expected value of __cpp_consteval. * g++.dg/cpp2a/spaceship-synth9.C: Adjust dg-error. * g++.dg/cpp2a/consteval-prop1.C: New test. * g++.dg/cpp2a/consteval-prop10.C: New test. * g++.dg/cpp2a/consteval-prop11.C: New test. * g++.dg/cpp2a/consteval-prop12.C: New test. * g++.dg/cpp2a/consteval-prop13.C: New test. * g++.dg/cpp2a/consteval-prop14.C: New test. * g++.dg/cpp2a/consteval-prop15.C: New test. * g++.dg/cpp2a/consteval-prop16.C: New test. * g++.dg/cpp2a/consteval-prop17.C: New test. * g++.dg/cpp2a/consteval-prop18.C: New test. * g++.dg/cpp2a/consteval-prop19.C: New test. * g++.dg/cpp2a/consteval-prop2.C: New test. * g++.dg/cpp2a/consteval-prop3.C: New test. * g++.dg/cpp2a/consteval-prop4.C: New test. * g++.dg/cpp2a/consteval-prop5.C: New test. * g++.dg/cpp2a/consteval-prop6.C: New test. * g++.dg/cpp2a/consteval-prop7.C: New test. * g++.dg/cpp2a/consteval-prop8.C: New test. * g++.dg/cpp2a/consteval-prop9.C: New test. --- gcc/c-family/c-cppbuiltin.cc | 2 +- gcc/c-family/c.opt | 4 + gcc/cp/call.cc | 2 +- gcc/cp/constexpr.cc | 2 +- gcc/cp/cp-gimplify.cc | 434 ++++++++++++++++-- gcc/cp/cp-tree.h | 17 +- gcc/cp/decl.cc | 5 +- gcc/cp/decl2.cc | 10 +- gcc/cp/module.cc | 4 + gcc/cp/typeck.cc | 6 +- gcc/doc/invoke.texi | 8 + gcc/testsuite/g++.dg/cpp23/consteval-if10.C | 7 +- gcc/testsuite/g++.dg/cpp23/consteval-if2.C | 14 +- gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C | 4 +- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C | 4 +- gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C | 3 + gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C | 169 +++++++ gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C | 41 ++ gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C | 49 ++ gcc/testsuite/g++.dg/cpp2a/consteval-prop12.C | 30 ++ gcc/testsuite/g++.dg/cpp2a/consteval-prop13.C | 23 + gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C | 78 ++++ gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C | 107 +++++ gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C | 73 +++ gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C | 17 + gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C | 20 + gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C | 7 + gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C | 88 ++++ gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C | 27 ++ gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C | 30 ++ gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C | 27 ++ gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C | 59 +++ gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C | 76 +++ gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C | 82 ++++ gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C | 67 +++ gcc/testsuite/g++.dg/cpp2a/consteval11.C | 18 + gcc/testsuite/g++.dg/cpp2a/consteval3.C | 4 +- gcc/testsuite/g++.dg/cpp2a/consteval34.C | 8 + gcc/testsuite/g++.dg/cpp2a/consteval9.C | 2 + gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C | 4 +- gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C | 2 +- .../comparisons/categories/zero_neg.cc | 1 + .../testsuite/std/format/string_neg.cc | 2 +- 43 files changed, 1563 insertions(+), 74 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop12.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop13.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc index 8904ac85015..3c8eaed7ad3 100644 --- a/gcc/c-family/c-cppbuiltin.cc +++ b/gcc/c-family/c-cppbuiltin.cc @@ -1058,7 +1058,7 @@ c_cpp_builtins (cpp_reader *pfile) cpp_define (pfile, "__cpp_constexpr=202002L"); cpp_define (pfile, "__cpp_constexpr_in_decltype=201711L"); cpp_define (pfile, "__cpp_conditional_explicit=201806L"); - cpp_define (pfile, "__cpp_consteval=201811L"); + cpp_define (pfile, "__cpp_consteval=202211L"); cpp_define (pfile, "__cpp_constinit=201907L"); cpp_define (pfile, "__cpp_deduction_guides=201907L"); cpp_define (pfile, "__cpp_nontype_template_args=201911L"); diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 44b9c862c14..41b20e71005 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1874,6 +1874,10 @@ fhuge-objects C++ ObjC++ WarnRemoved No longer supported. +fimmediate-escalation +C++ ObjC++ Var(flag_immediate_escalation) Init(1) +Implement P2564 for consteval propagation. + fimplement-inlines C++ ObjC++ Var(flag_implement_inlines) Init(1) Export functions even if they can be inlined. diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index e8dafbd8ba6..4101857da11 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -9726,7 +9726,7 @@ in_immediate_context () /* Return true if a call to FN with number of arguments NARGS is an immediate invocation. */ -static bool +bool immediate_invocation_p (tree fn) { return (TREE_CODE (fn) == FUNCTION_DECL diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 0f948db7c2d..a6ddaebce48 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -3854,7 +3854,7 @@ cxx_eval_conditional_expression (const constexpr_ctx *ctx, tree t, if (ctx->manifestly_const_eval != mce_true && !in_immediate_context () && cp_fold_immediate (&TREE_OPERAND (t, zero_p ? 1 : 2), - ctx->manifestly_const_eval)) + ctx->manifestly_const_eval, /*escalate_p=*/false)) { *non_constant_p = true; return t; diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc index bdf6e5f98ff..deda09a805b 100644 --- a/gcc/cp/cp-gimplify.cc +++ b/gcc/cp/cp-gimplify.cc @@ -43,6 +43,15 @@ along with GCC; see the file COPYING3. If not see #include "omp-general.h" #include "opts.h" +/* A vec of FUNCTION_DECLs. They are immediate-escalating functions and may + become consteval */ +static GTY(()) vec *immediate_escalating_decls; +/* Keep track of forward references to immediate-escalating functions in + case they become consteval. This vector contains ADDR_EXPRs and + PTRMEM_CSTs; it also stores FUNCTION_DECLs that had an escalating + function call in them, to check that they can be evaluated to a constant. */ +static GTY(()) vec *deferred_escalating_exprs; + /* Flags for cp_fold and cp_fold_r. */ enum fold_flags { @@ -55,6 +64,8 @@ enum fold_flags { ff_mce_false = 1 << 1, /* Whether we're being called from cp_fold_immediate. */ ff_fold_immediate = 1 << 2, + /* Whether we're escalating immediate-escalating functions. */ + ff_escalating = 1 << 3, }; using fold_flags_t = int; @@ -428,6 +439,176 @@ lvalue_has_side_effects (tree e) return TREE_SIDE_EFFECTS (e); } +/* Return true if FN is an immediate-escalating function. */ + +static bool +immediate_escalating_function_p (tree fn) +{ + if (!fn || !flag_immediate_escalation) + return false; + + gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL); + + /* An immediate-escalating function is + -- the call operator of a lambda that is not declared with the consteval + specifier */ + if (LAMBDA_FUNCTION_P (fn) && !DECL_IMMEDIATE_FUNCTION_P (fn)) + return true; + /* -- a defaulted special member function that is not declared with the + consteval specifier */ + special_function_kind sfk = special_memfn_p (fn); + if (sfk != sfk_none + && DECL_DEFAULTED_FN (fn) + && !DECL_IMMEDIATE_FUNCTION_P (fn)) + return true; + /* -- a function that results from the instantiation of a templated entity + defined with the constexpr specifier. */ + return is_instantiation_of_constexpr (fn); +} + +/* Promote FN to an immediate function, including its clones, if it is + an immediate-escalating function. Return true if we did promote; + false otherwise. */ + +static bool +maybe_promote_function_to_consteval (tree fn) +{ + if (fn + && !DECL_IMMEDIATE_FUNCTION_P (fn) + && immediate_escalating_function_p (fn)) + { + SET_DECL_IMMEDIATE_FUNCTION_P (fn); + DECL_ESCALATED_P (fn) = true; + tree clone; + FOR_EACH_CLONE (clone, fn) + { + SET_DECL_IMMEDIATE_FUNCTION_P (clone); + DECL_ESCALATED_P (clone) = true; + } + return true; + } + + return false; +} + +/* Remember that the current function declaration contains a call to + a function that might be promoted to consteval later. */ + +static void +maybe_store_cfun_for_late_checking () +{ + if (!current_function_decl + || !flag_immediate_escalation + || immediate_escalating_function_p (current_function_decl)) + return; + + if (deferred_escalating_exprs + /* Don't put duplicates into the vec so that we don't walk + a function multiple times. Most likely the last one + added will be the same. */ + && (deferred_escalating_exprs->last () == current_function_decl + || deferred_escalating_exprs->contains (current_function_decl))) + return; + + vec_safe_push (deferred_escalating_exprs, current_function_decl); +} + +/* Find an immediate-escalating expression or conversion in *TP. + If DATA_ is non-null, this function will promote function to + consteval as it goes; otherwise, we're just looking for what + made a function consteval, for diagnostic purposes. This + function assumes that *TP was instantiated. */ + +static tree +find_escalating_expr_r (tree *tp, int *walk_subtrees, void *data) +{ + /* The function whose body we're traversing. Used to promote the current + function to consteval. */ + tree caller = data ? *static_cast(data) : NULL_TREE; + tree t = *tp; + const tree_code code = TREE_CODE (t); + + if (TYPE_P (t) || unevaluated_p (code)) + { +bail: + *walk_subtrees = 0; + return NULL_TREE; + } + + tree decl; + tree call; + + switch (code) + { + case CALL_EXPR: + decl = cp_get_callee_fndecl_nofold (t); + call = t; + break; + case ADDR_EXPR: + if (TREE_CODE (TREE_OPERAND (t, 0)) != FUNCTION_DECL) + goto bail; + decl = TREE_OPERAND (t, 0); + call = NULL_TREE; + break; + case PTRMEM_CST: + if (TREE_CODE (PTRMEM_CST_MEMBER (t)) != FUNCTION_DECL) + goto bail; + decl = PTRMEM_CST_MEMBER (t); + call = NULL_TREE; + break; + default: + return NULL_TREE; + } + + /* Now, voyager, sail thou forth, to seek and find. */ + if (!decl) + goto bail; + + /* Not consteval yet, but could be. Have to look deeper. */ + if (!DECL_IMMEDIATE_FUNCTION_P (decl) + && immediate_escalating_function_p (decl) + && !DECL_ESCALATED_P (decl)) + { + /* Set before the actual walk to avoid endless recursion. */ + DECL_ESCALATED_P (decl) = true; + cp_walk_tree (&DECL_SAVED_TREE (decl), find_escalating_expr_r, + caller ? &decl : nullptr, nullptr); + } + + /* If it turned out to be consteval, maybe promote the caller. */ + if (DECL_IMMEDIATE_FUNCTION_P (decl) + && (!call || cxx_constant_value (call, tf_none) == error_mark_node)) + { + /* We found the escalating expression. */ + maybe_promote_function_to_consteval (caller); + *walk_subtrees = 0; + return t; + } + + return NULL_TREE; +} + +/* Maybe say that FN (a function decl with DECL_IMMEDIATE_FUNCTION_P set) + was initially not an immediate function, but was promoted to one because + its body contained an immediate-escalating expression or conversion. */ + +static void +maybe_explain_promoted_consteval (location_t loc, tree fn) +{ + if (DECL_ESCALATED_P (fn)) + { + /* See if we can figure out what made the function consteval. */ + tree x = cp_walk_tree (&DECL_SAVED_TREE (fn), find_escalating_expr_r, + NULL_TREE, nullptr); + if (x) + inform (cp_expr_loc_or_loc (x, loc), + "%qD was promoted to an immediate function because its " + "body contains an immediate-escalating expression %qE", fn, x); + else + inform (loc, "%qD was promoted to an immediate function", fn); + } +} + /* Gimplify *EXPR_P as rvalue into an expression that can't be modified by expressions with side-effects in other operands. */ @@ -485,6 +666,41 @@ cp_gimplify_arg (tree *arg_p, gimple_seq *pre_p, location_t call_location, } +/* Figure out if DECL should be promoted to consteval and if so, maybe also + promote the function we are in currently. CALL is the CALL_EXPR of DECL. + EVALP is where we may store the result of cxx_constant_value so that we + don't have to evaluate the same tree again in cp_fold_immediate_r. */ + +static void +maybe_escalate_decl_and_cfun (tree decl, tree call, tree *evalp) +{ + /* Compiler-generated functions don't seem like good candidates for + promoting. */ + if (cp_unevaluated_operand || DECL_ARTIFICIAL (decl)) + return; + + /* What we're calling is not a consteval function but it may become + one. This requires recursing; DECL may be promoted to consteval + because it contains an escalating expression E, but E itself may + have to be promoted first, etc. */ + if (!DECL_IMMEDIATE_FUNCTION_P (decl) + && immediate_escalating_function_p (decl) + && !DECL_ESCALATED_P (decl)) + cp_walk_tree (&DECL_SAVED_TREE (decl), find_escalating_expr_r, + &decl, nullptr); + + /* In turn, maybe promote the function we find ourselves in... */ + if (DECL_IMMEDIATE_FUNCTION_P (decl) + /* ...but not if the call to DECL was constant; that is the + "an immediate invocation that is not a constant expression" + case. We do this here and not in find_escalating_expr_r, + because DECL could have already been consteval and we'd + never call f_e_e_r. */ + && (*evalp = cxx_constant_value (call, tf_none), + *evalp == error_mark_node)) + maybe_promote_function_to_consteval (current_function_decl); +} + /* Do C++-specific gimplification. Args are as for gimplify_expr. */ int @@ -746,7 +962,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p) if (ret != GS_ERROR) { tree decl = cp_get_callee_fndecl_nofold (*expr_p); - if (decl && fndecl_built_in_p (decl, BUILT_IN_FRONTEND)) + if (!decl) + break; + if (fndecl_built_in_p (decl, BUILT_IN_FRONTEND)) switch (DECL_FE_FUNCTION_CODE (decl)) { case CP_BUILT_IN_IS_CONSTANT_EVALUATED: @@ -771,6 +989,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p) default: break; } + else + /* All consteval functions should have been processed by now. */ + gcc_checking_assert (!immediate_invocation_p (decl)); } break; @@ -1031,6 +1252,20 @@ struct cp_genericize_data bool handle_invisiref_parm_p; }; +/* Emit an error about taking the address of an immediate function. + EXPR is the whole expression; DECL is the immediate function. */ + +static void +taking_address_of_imm_fn_error (tree expr, tree decl) +{ + auto_diagnostic_group d; + const location_t loc = (TREE_CODE (expr) == PTRMEM_CST + ? PTRMEM_CST_LOCATION (expr) + : EXPR_LOCATION (expr)); + error_at (loc, "taking address of an immediate function %qD", decl); + maybe_explain_promoted_consteval (loc, decl); +} + /* A subroutine of cp_fold_r to handle immediate functions. */ static tree @@ -1041,16 +1276,17 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_) /* The purpose of this is not to emit errors for mce_unknown. */ const tsubst_flags_t complain = (data->flags & ff_mce_false ? tf_error : tf_none); + const tree_code code = TREE_CODE (stmt); /* No need to look into types or unevaluated operands. NB: This affects cp_fold_r as well. */ - if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt))) + if (TYPE_P (stmt) || unevaluated_p (code)) { *walk_subtrees = 0; return NULL_TREE; } - switch (TREE_CODE (stmt)) + switch (code) { /* Unfortunately we must handle code like false ? bar () : 42 @@ -1073,20 +1309,42 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_) from cp_fold_r and we must let it recurse on the expression with cp_fold. */ break; + case PTRMEM_CST: - if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL - && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt))) - { - if (!data->pset.add (stmt) && (complain & tf_error)) - { - error_at (PTRMEM_CST_LOCATION (stmt), - "taking address of an immediate function %qD", - PTRMEM_CST_MEMBER (stmt)); - *stmt_p = build_zero_cst (TREE_TYPE (stmt)); - } - return error_mark_node; - } - break; + case ADDR_EXPR: + { + tree decl = (code == PTRMEM_CST + ? PTRMEM_CST_MEMBER (stmt) + : TREE_OPERAND (stmt, 0)); + if (TREE_CODE (decl) != FUNCTION_DECL) + break; + if (code == ADDR_EXPR && ADDR_EXPR_DENOTES_CALL_P (stmt)) + break; + if (immediate_invocation_p (decl)) + { + if (maybe_promote_function_to_consteval (current_function_decl)) + break; + if (complain & tf_error) + { + /* ??? Why don't we use data->pset for ADDR_EXPR too? */ + if (code == ADDR_EXPR || !data->pset.add (stmt)) + { + taking_address_of_imm_fn_error (stmt, decl); + *stmt_p = build_zero_cst (TREE_TYPE (stmt)); + } + /* If we're giving hard errors, continue the walk rather than + bailing out after the first error. */ + break; + } + return error_mark_node; + } + /* Not consteval yet, but could become one, in which case it's invalid + to take its address. */ + else if (!(data->flags & ff_fold_immediate) + && immediate_escalating_function_p (decl)) + vec_safe_push (deferred_escalating_exprs, stmt); + break; + } /* Expand immediate invocations. */ case CALL_EXPR: @@ -1094,33 +1352,54 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_) if (tree fn = cp_get_callee (stmt)) if (TREE_CODE (fn) != ADDR_EXPR || ADDR_EXPR_DENOTES_CALL_P (fn)) if (tree fndecl = cp_get_fndecl_from_callee (fn, /*fold*/false)) - if (DECL_IMMEDIATE_FUNCTION_P (fndecl)) - { - stmt = cxx_constant_value (stmt, complain); - if (stmt == error_mark_node) - { - if (complain & tf_error) - *stmt_p = error_mark_node; - return error_mark_node; - } - *stmt_p = stmt; - } - break; - - case ADDR_EXPR: - if (TREE_CODE (TREE_OPERAND (stmt, 0)) == FUNCTION_DECL - && DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (stmt, 0)) - && !ADDR_EXPR_DENOTES_CALL_P (stmt)) - { - if (complain & tf_error) { - error_at (EXPR_LOCATION (stmt), - "taking address of an immediate function %qD", - TREE_OPERAND (stmt, 0)); - *stmt_p = build_zero_cst (TREE_TYPE (stmt)); + tree eval = NULL_TREE; + if (data->flags & ff_escalating) + maybe_escalate_decl_and_cfun (fndecl, stmt, &eval); + + /* [expr.const]p16 "An expression or conversion is + immediate-escalating if it is not initially in an immediate + function context and it is either + -- an immediate invocation that is not a constant expression + and is not a subexpression of an immediate invocation." + + If we are in an immediate-escalating function, the + immediate-escalating expression or conversion makes it an + immediate function. So STMT does not need to produce + a constant expression. */ + if (immediate_invocation_p (fndecl)) + { + tree e = eval ? eval : cxx_constant_value (stmt, tf_none); + if (e == error_mark_node) + { + if (maybe_promote_function_to_consteval + (current_function_decl)) + break; + if (complain & tf_error) + { + auto_diagnostic_group d; + location_t loc = cp_expr_loc_or_input_loc (stmt); + error_at (loc, "call to consteval function %qE is " + "not a constant expression", stmt); + /* Explain why it's not a constant expression. */ + *stmt_p = cxx_constant_value (stmt, complain); + maybe_explain_promoted_consteval (loc, fndecl); + /* Don't return error_mark_node, it would stop our + tree walk. */ + break; + } + return error_mark_node; + } + /* We've evaluated the consteval function call. */ + *stmt_p = e; + } + /* We've encountered a function call that may turn out to be + consteval later. Store its caller so that we can ensure + that the call is a constant expression. */ + else if (!(data->flags & ff_fold_immediate) + && immediate_escalating_function_p (fndecl)) + maybe_store_cfun_for_late_checking (); } - return error_mark_node; - } break; default: @@ -1132,10 +1411,11 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_) /* A wrapper around cp_fold_immediate_r. Return true if we found a non-constant immediate function, or taking the address of an - immediate function. */ + immediate function. If ESCALATE_P, tell cp_fold_immediate_r to + escalate immediate-escalating functions. */ bool -cp_fold_immediate (tree *tp, mce_value manifestly_const_eval) +cp_fold_immediate (tree *tp, mce_value manifestly_const_eval, bool escalate_p) { if (cxx_dialect <= cxx17) return false; @@ -1143,9 +1423,13 @@ cp_fold_immediate (tree *tp, mce_value manifestly_const_eval) fold_flags_t flags = ff_fold_immediate; if (manifestly_const_eval == mce_false) flags |= ff_mce_false; + if (escalate_p) + flags |= ff_escalating; cp_fold_data data (flags); - return !!cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data); + int save_errorcount = errorcount; + tree r = cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data); + return r != NULL_TREE || errorcount > save_errorcount; } /* Perform any pre-gimplification folding of C++ front end trees to @@ -1290,6 +1574,68 @@ cp_fold_function (tree fndecl) cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &data, NULL); } +/* FN is not a consteval function, but may become one. Remember to + escalate it after all pending templates have been instantiated. */ + +void +maybe_store_immediate_escalating_fn (tree fn) +{ + if (cxx_dialect >= cxx20 + && flag_immediate_escalation + && !DECL_IMMEDIATE_FUNCTION_P (fn) + && immediate_escalating_function_p (fn)) + vec_safe_push (immediate_escalating_decls, fn); +} + +/* We've stashed immediate-escalating functions. Now see if they indeed + ought to be promoted to consteval. */ + +void +process_pending_immediate_escalating_fns () +{ + /* This will be null for -fno-immediate-escalation. */ + if (!immediate_escalating_decls) + return; + + while (!immediate_escalating_decls->is_empty ()) + { + tree fndecl = immediate_escalating_decls->pop (); + temp_override cfd (current_function_decl, fndecl); + cp_fold_immediate (&DECL_SAVED_TREE (fndecl), mce_false, + /*escalate_p=*/true); + } +} + +/* We've escalated every function that could have been promoted to + consteval. Check that we are not taking the address of a consteval + function. */ + +void +check_immediate_escalating_refs () +{ + if (!deferred_escalating_exprs) + return; + + while (!deferred_escalating_exprs->is_empty ()) + { + tree ref = deferred_escalating_exprs->pop (); + if (TREE_CODE (ref) == FUNCTION_DECL) + /* We saw a function call to an immediate-escalating function in + the body of REF. Check that it's a constant if it was promoted + to consteval. */ + cp_fold_immediate (&DECL_SAVED_TREE (ref), mce_false, + /*escalate_p=*/false); + else + { + tree decl = (TREE_CODE (ref) == PTRMEM_CST + ? PTRMEM_CST_MEMBER (ref) + : TREE_OPERAND (ref, 0)); + if (DECL_IMMEDIATE_FUNCTION_P (decl)) + taking_address_of_imm_fn_error (ref, decl); + } + } +} + /* Turn SPACESHIP_EXPR EXPR into GENERIC. */ static tree genericize_spaceship (tree expr) diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 6e34952da99..f866f259f4e 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -2938,8 +2938,9 @@ struct GTY(()) lang_decl_fn { unsigned maybe_deleted : 1; unsigned coroutine_p : 1; unsigned implicit_constexpr : 1; + unsigned escalated_p : 1; - unsigned spare : 9; + unsigned spare : 8; /* 32-bits padding on 64-bit host. */ @@ -3391,6 +3392,14 @@ struct GTY(()) lang_decl { #define DECL_MAYBE_DELETED(NODE) \ (LANG_DECL_FN_CHECK (NODE)->maybe_deleted) +/* Nonzero for FUNCTION_DECL means that this function's body has been + checked for immediate-escalating expressions and maybe promoted. It + does *not* mean the function is consteval. It must not be set in + a function that was marked consteval by the user, so that we can + distinguish between explicitly consteval functions and promoted consteval + functions. */ +#define DECL_ESCALATED_P(NODE) (LANG_DECL_FN_CHECK (NODE)->escalated_p) + /* True (in a FUNCTION_DECL) if NODE is a virtual function that is an invalid overrider for a function from a base class. Once we have complained about an invalid overrider we avoid complaining about it @@ -6736,6 +6745,7 @@ extern tree perform_direct_initialization_if_possible (tree, tree, bool, extern vec *resolve_args (vec*, tsubst_flags_t); extern tree in_charge_arg_for_name (tree); extern bool in_immediate_context (); +extern bool immediate_invocation_p (tree); extern tree build_cxx_call (tree, int, tree *, tsubst_flags_t, tree = NULL_TREE); @@ -8379,7 +8389,10 @@ extern tree process_stmt_assume_attribute (tree, tree, location_t); extern bool simple_empty_class_p (tree, tree, tree_code); extern tree fold_builtin_source_location (const_tree); extern tree get_source_location_impl_type (); -extern bool cp_fold_immediate (tree *, mce_value); +extern bool cp_fold_immediate (tree *, mce_value, bool); +extern void check_immediate_escalating_refs (); +extern void maybe_store_immediate_escalating_fn (tree); +extern void process_pending_immediate_escalating_fns (); /* in name-lookup.cc */ extern tree strip_using_decl (tree); diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index 255c4026bdb..5480e4167ab 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -18342,7 +18342,10 @@ finish_function (bool inline_p) if (!processing_template_decl && !DECL_IMMEDIATE_FUNCTION_P (fndecl) && !DECL_OMP_DECLARE_REDUCTION_P (fndecl)) - cp_fold_function (fndecl); + { + cp_fold_function (fndecl); + maybe_store_immediate_escalating_fn (fndecl); + } /* Set up the named return value optimization, if we can. Candidate variables are selected in check_return_expr. */ diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc index 344e19ec98b..a67bdb4db6f 100644 --- a/gcc/cp/decl2.cc +++ b/gcc/cp/decl2.cc @@ -5294,7 +5294,15 @@ c_parse_final_cleanups (void) if (static_init_fini_fns[true]) for (auto iter : *static_init_fini_fns[true]) iter.second = nreverse (iter.second); - + + /* Now we've instantiated all templates. Now we can escalate the functions + we squirreled away earlier. */ + if (cxx_dialect >= cxx20) + { + process_pending_immediate_escalating_fns (); + check_immediate_escalating_refs (); + } + /* Then, do the Objective-C stuff. This is where all the Objective-C module stuff gets generated (symtab, class/protocol/selector lists etc). This must be done after C++ diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index e3fb2299d93..02c50ac6a11 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -5685,6 +5685,8 @@ trees_out::lang_decl_bools (tree t) WB (lang->u.fn.has_dependent_explicit_spec_p); WB (lang->u.fn.immediate_fn_p); WB (lang->u.fn.maybe_deleted); + /* We do not stream lang->u.implicit_constexpr nor + lang->u.escalated_p. */ goto lds_min; case lds_decomp: /* lang_decl_decomp. */ @@ -5753,6 +5755,8 @@ trees_in::lang_decl_bools (tree t) RB (lang->u.fn.has_dependent_explicit_spec_p); RB (lang->u.fn.immediate_fn_p); RB (lang->u.fn.maybe_deleted); + /* We do not stream lang->u.implicit_constexpr nor + lang->u.escalated_p. */ goto lds_min; case lds_decomp: /* lang_decl_decomp. */ diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc index 8132bd7fccc..71e6db896af 100644 --- a/gcc/cp/typeck.cc +++ b/gcc/cp/typeck.cc @@ -7244,11 +7244,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain) complain); } - /* For addresses of immediate functions ensure we have EXPR_LOCATION - set for possible later diagnostics. */ + /* Ensure we have EXPR_LOCATION set for possible later diagnostics. */ if (TREE_CODE (val) == ADDR_EXPR - && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL - && DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (val, 0))) + && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL) SET_EXPR_LOCATION (val, input_location); return val; diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 7c5f81d9783..7aa5d620da2 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -219,6 +219,7 @@ in the following sections. -fno-elide-constructors -fno-enforce-eh-specs -fno-gnu-keywords +-fno-immediate-escalation -fno-implicit-templates -fno-implicit-inline-templates -fno-implement-inlines @@ -3364,6 +3365,13 @@ word as an identifier. You can use the keyword @code{__typeof__} instead. This option is implied by the strict ISO C++ dialects: @option{-ansi}, @option{-std=c++98}, @option{-std=c++11}, etc. +@opindex fno-immediate-escalation +@opindex fimmediate-escalation +@item -fno-immediate-escalation +Do not enable immediate function escalation whereby certain functions +can be promoted to consteval, as specified in P2564R3. This option is +turned on by default; it is only effective in C++20 mode or later. + @opindex fimplicit-constexpr @item -fimplicit-constexpr Make inline functions implicitly constexpr, if they satisfy the diff --git a/gcc/testsuite/g++.dg/cpp23/consteval-if10.C b/gcc/testsuite/g++.dg/cpp23/consteval-if10.C index 4c0523fe1d0..b8709beba85 100644 --- a/gcc/testsuite/g++.dg/cpp23/consteval-if10.C +++ b/gcc/testsuite/g++.dg/cpp23/consteval-if10.C @@ -2,6 +2,9 @@ // { dg-do compile { target c++20 } } // { dg-options "" } +// We used to give errors but the lambdas are now promoted to consteval +// and are in a immediate function context, so no errors. + consteval int foo (int x) { return x; } constexpr int @@ -10,7 +13,7 @@ bar (int x) int r = 0; if consteval // { dg-warning "'if consteval' only available with" "" { target c++20_only } } { - auto y = [=] { foo (x); }; // { dg-error "'x' is not a constant expression" } + auto y = [=] { foo (x); }; y (); } return r; @@ -23,7 +26,7 @@ baz (T x) T r = 0; if consteval // { dg-warning "'if consteval' only available with" "" { target c++20_only } } { - auto y = [=] { foo (x); }; // { dg-error "'x' is not a constant expression" } + auto y = [=] { foo (x); }; y (); } return r; diff --git a/gcc/testsuite/g++.dg/cpp23/consteval-if2.C b/gcc/testsuite/g++.dg/cpp23/consteval-if2.C index b2c5472b7de..3b258711ce6 100644 --- a/gcc/testsuite/g++.dg/cpp23/consteval-if2.C +++ b/gcc/testsuite/g++.dg/cpp23/consteval-if2.C @@ -33,7 +33,7 @@ baz (int x) int r = 0; if not consteval // { dg-warning "'if consteval' only available with" "" { target c++20_only } } { - r += foo (x); // { dg-error "'x' is not a constant expression" } + r += foo (x); // { dg-error "not a constant expression" } } else { @@ -45,11 +45,11 @@ baz (int x) } else { - r += foo (8 * x); // { dg-error "'x' is not a constant expression" } + r += foo (8 * x); // { dg-error "is not a constant expression" } } if ! consteval // { dg-warning "'if consteval' only available with" "" { target c++20_only } } { - r += foo (32 * x);// { dg-error "'x' is not a constant expression" } + r += foo (32 * x);// { dg-error "not a constant expression" } } if consteval // { dg-warning "'if consteval' only available with" "" { target c++20_only } } { @@ -98,7 +98,7 @@ corge (T x) T r = 0; if not consteval // { dg-warning "'if consteval' only available with" "" { target c++20_only } } { - r += foo (x); // { dg-error "'x' is not a constant expression" } + r += foo (x); } else { @@ -110,11 +110,11 @@ corge (T x) } else { - r += foo (8 * x); // { dg-error "is not a constant expression" } + r += foo (8 * x); } if ! consteval // { dg-warning "'if consteval' only available with" "" { target c++20_only } } { - r += foo (32 * x);// { dg-error "is not a constant expression" } + r += foo (32 * x); } if consteval // { dg-warning "'if consteval' only available with" "" { target c++20_only } } { @@ -126,5 +126,5 @@ corge (T x) int garply (int x) { - return corge (x); + return corge (x); // { dg-error "is not a constant expression" } } diff --git a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C index 9e29b01adc1..2b21bd1bc0d 100644 --- a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C +++ b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C @@ -480,8 +480,8 @@ #ifndef __cpp_consteval # error "__cpp_consteval" -#elif __cpp_consteval != 201811 -# error "__cpp_consteval != 201811" +#elif __cpp_consteval != 202211L +# error "__cpp_consteval != 202211L" #endif #ifndef __cpp_concepts diff --git a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C index 0977d964fe0..b1b9be2d24a 100644 --- a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C +++ b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C @@ -480,8 +480,8 @@ #ifndef __cpp_consteval # error "__cpp_consteval" -#elif __cpp_consteval != 201811 -# error "__cpp_consteval != 201811" +#elif __cpp_consteval != 202211L +# error "__cpp_consteval != 202211L" #endif #ifndef __cpp_concepts diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C index 63f4f1d526a..edf8c4a139d 100644 --- a/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C @@ -19,10 +19,13 @@ template void VerifyHash(fixed_string s) { s.size(0); // { dg-bogus "" } s.size(-1); // { dg-message "expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } s.size_static(0); // { dg-bogus "" } s.size_static(-1); // { dg-message "expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } fixed_string::size_static(0); // { dg-bogus "" } fixed_string::size_static(-1); // { dg-message "expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } s(); // { dg-bogus "" } } diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C new file mode 100644 index 00000000000..5e7b208113f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C @@ -0,0 +1,169 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// Some of these were cribbed from clang's cxx2b-consteval-propagate.cpp. + +consteval int id(int i) { return i; } + +template +constexpr int +f0 (T t) +{ + // OK, f0 promoted to consteval. + return id (t); // { dg-message "immediate-escalating expression .id\\(t\\)." } +} + +constexpr auto a0 = f0 (3); + +// As a consequence of f0 being promoted to an immediate function, we +// can't take its address. +auto p0 = &f0; // { dg-error "taking address of an immediate function" } + +template +constexpr int +f1 (T t) +{ + // OK, f1 promoted to consteval. + return t + id (t); // { dg-message "immediate-escalating expression .id\\(t\\)." } +} + +constexpr auto a1 = f1 (3); + +// As a consequence of f1 being promoted to an immediate function, we +// can't take its address. +auto p1 = &f1; // { dg-error "taking address of an immediate function" } + +template +constexpr int +f2 (T) +{ + // This produces a constant; f2 *not* promoted to consteval. + return id (42); +} + +// ... so we can take its address. +auto p2 = &f2; + +constexpr int +f3 (int i) +{ + // f3 isn't a function template and those don't get upgraded to consteval. + return id (i); // { dg-error "not a constant expression" } +} + +auto p3 = &f3; + +template +constexpr int +f4 (T t) +{ + auto p = id; // { dg-message "immediate-escalating expression .id." } + (void) p; + return t; +} + +auto p6 = &f4; // { dg-error "taking address of an immediate function" } + +static_assert (f4 (42) == 42); + +// Constructors. +consteval int zero (int) +{ + return 0; +} + +struct A { + // A::A(auto) promoted to consteval. + constexpr A(auto i) { zero (i); } +}; + +constexpr void +f5 (auto i) +{ + A a{i}; +} + +constexpr void +f5_nt (int i) +{ + A a{i}; // { dg-error "call to consteval function|not a constant" } +} + +void +f6 () +{ + f5 (0); +} + +struct B { + constexpr B(int) { } +}; + +B b1(f0((f1(7)))); + +template +constexpr int cid(T t) { return t; } + +auto p4 = &cid; +auto p5 = &cid; + +int g = 7; // { dg-message ".int g. is not const" } + +B b2(f0(cid(g))); // { dg-error "call to consteval function|not usable" } + +struct C { + consteval C (int) {}; +}; + +constexpr int +f7 (auto t) +{ + C c(t); // { dg-message "immediate-escalating expression .c.C::C\\(t\\)." } + return 0; +} + +int i1 = f7 (g); // { dg-error "call to consteval function|not usable" } + +struct Y { + int y; + int x = id (y); + consteval Y (int i) : y (id (i)) {} +}; + +Y y1(1); +Y y2(g); // { dg-error "call to consteval function|not usable" } + +struct Y2 { + int y; + int x = id (y); + constexpr Y2 (auto i) : y (id (i)) {} +}; + +Y2 y3(1); +Y2 y4(g); // { dg-error "call to consteval function|not usable" } + +auto l1 = [](int i) constexpr { + int t = id (i); + return id (0); +}; + +int (*pl1)(int) = l1; // { dg-error "call to consteval function|returns address of immediate function" } + +auto l2 = [](int i) { + int t = id (i); + return id (0); +}; + +int (*pl2)(int) = l2; // { dg-error "call to consteval function|returns address of immediate function" } + +// Not defined = won't produce a constant expression. +consteval int undef (); // { dg-warning "used but never defined" } + +struct S { + int a = [] { return undef (); }(); +}; + +struct S2 { // { dg-error "used before its definition" } + int a = [] (int u = undef ()) { + return u; + }(); +} s2; // { dg-error "call to consteval function" } diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C new file mode 100644 index 00000000000..4e33e6e3d0e --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C @@ -0,0 +1,41 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// Test default arguments. + +consteval int id (int i) { return i; } + +template +constexpr int +f1 (int i = id (42)) +{ + return i; +} + +int non_const; // { dg-message ".int non_const. is not const" } + +template +constexpr int +f2 (int i = id (non_const)) +{ + return i; +} + +constexpr int +f3 (auto) +{ + return f2(); // { dg-message "contains an immediate-escalating expression .id\\(non_const\\)." } +} + +auto a = &f3; // { dg-error "taking address of an immediate function" } + +void +g (int i) +{ + f1 (42); + f1 (i); + f1 (); + f2 (42); + f2 (i); + f2 (); // { dg-error "call to consteval function .id\\(non_const\\). is not a constant expression" } +// { dg-error ".non_const. is not usable in a constant expression" "" { target *-*-* } .-1 } +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C new file mode 100644 index 00000000000..aca9675cd53 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C @@ -0,0 +1,49 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// { dg-options "-fdiagnostics-show-caret" } +// Test diagnostic. + +consteval int id (int i) { return i; } +constexpr int foo (int i ) { return i; } + +constexpr int +foobar (auto i) +{ + return i + id (i); + /* { dg-begin-multiline-output "" } + return i + id (i); + ~~~^~~ + { dg-end-multiline-output "" } */ +} + +void +g (int x) +{ + foobar (x); // { dg-error "10:call to consteval function .foobar\\(x\\). is not a constant expression" } +// { dg-error ".x. is not a constant expression" "" { target *-*-* } .-1 } + /* { dg-begin-multiline-output "" } +foobar (x); + ~~~~~~~^~~ + { dg-end-multiline-output "" } */ +} + +constexpr int +f2 (auto i) +{ + auto p = &id; + /* { dg-begin-multiline-output "" } + auto p = &id; + ^~~ + { dg-end-multiline-output "" } */ + return p (i); +} + +void +g2 (int x) +{ + f2 (x); // { dg-error "6:call to consteval function .f2\\(x\\). is not a constant expression|not a constant expression" } + /* { dg-begin-multiline-output "" } +f2 (x); + ~~~^~~ + { dg-end-multiline-output "" } */ +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop12.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop12.C new file mode 100644 index 00000000000..2949ab83af8 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop12.C @@ -0,0 +1,30 @@ +// P2564R3 +// { dg-do compile { target c++20 } } + +consteval int +zero (int) +{ + return 0; +} + +constexpr int +f (auto i) +{ + return zero (i); +} + +constexpr int +g (auto) +{ + // This call is a constant expression, so don't promote g. + return f (42); +} + +void +do_test () +{ + g (2); +} + +// Must work. +auto q = &g; diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop13.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop13.C new file mode 100644 index 00000000000..6c20b98a87c --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop13.C @@ -0,0 +1,23 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// Verify we don't recurse endlessly while determining whether a function +// should be propagated to consteval. + +consteval int id (int i) { return i; } + +constexpr int f2 (auto); + +constexpr int +f1 (auto i) +{ + return f2 (i); +} + +constexpr int +f2 (auto i) +{ + return f1 (i); +} + +auto p = &f1; +auto q = &f2; diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C new file mode 100644 index 00000000000..cdc1f6dc862 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C @@ -0,0 +1,78 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// Test more CALL_EXPRs in a function, some of which are escalating. + +consteval int id (int i) { return i; } +constexpr int neg (int i) { return -i; } +constexpr int foo (auto i) { return id (i); } + +constexpr int +f1 (auto i) +{ + auto x = id (i); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id\\(i\\)." } + auto y = neg (i); + return x + y; +} + +constexpr int +f2 (auto i) +{ + return neg (id (i)); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id\\(i\\)." } +} + +constexpr int +f3 (auto i) +{ + auto x = i + neg (neg (neg (id (neg (neg (i)))))); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id\\(neg\\(neg\\(i\\)\\)\\)." } + return x; +} + +constexpr int +f4 (auto i) +{ + return i + neg ((id (2 * i) + neg (i)) / 2); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id\\(\\(i \\* 2\\)\\)." } +} + +constexpr int +f5 (auto i) +{ + (void) neg (i); + (void) neg (i); + (void) neg (i); + (void) neg (i); + (void) neg (i); + (void) neg (i); + (void) +id (i); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id\\(i\\)." } + (void) neg (i); + return i; +} + +constexpr int +f6 (auto i) +{ + auto x = neg (i + foo (i)); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .foo\\(i\\)." } + return x; +} + +void +g (int i) +{ + f1 (i); // { dg-error "call to consteval function .f1\\(i\\). is not a constant expression" } +// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 } + f1 (42); + f2 (i); // { dg-error "call to consteval function .f2\\(i\\). is not a constant expression" } +// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 } + f2 (42); + f3 (i); // { dg-error "call to consteval function .f3\\(i\\). is not a constant expression" } +// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 } + f3 (42); + f4 (i); // { dg-error "call to consteval function .f4\\(i\\). is not a constant expression" } +// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 } + f4 (42); + f5 (i); // { dg-error "call to consteval function .f5\\(i\\). is not a constant expression" } +// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 } + f5 (42); + f6 (i); // { dg-error "call to consteval function .f6\\(i\\). is not a constant expression" } +// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 } + f6 (42); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C new file mode 100644 index 00000000000..3341c510a9f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C @@ -0,0 +1,107 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// { dg-options "-Wno-c++23-extensions" } + +consteval int id (int i) { return i; } + +constexpr int +f1 (auto i) +{ + auto p = &id; // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id." } + (void) p; + return i; +} + +constexpr int +f2 (auto i) +{ + return f1 (i); +} + +constexpr int +f3 (auto i) +{ + return f2 (i); +} + +constexpr int +f4 (auto i) +{ + return f3 (i); +} + +constexpr int +f5 (auto i) +{ + return f4 (i); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .f4\\(i\\)." } +} + +constexpr int +f6 (auto) +{ + // This call is a constant expression, so don't promote f6. + return f4 (42); +} + +constexpr int +f7 (auto i) +{ + if consteval { + auto p = &id; + (void) p; + } + return i; +} + +constexpr int +f8 (auto i) +{ + if not consteval { + (void) 0; + } else { + auto p = &id; + (void) p; + } + return i; +} + +constexpr int +f9 (auto i) +{ + if consteval { + return id(i); + } + return i; +} + +constexpr int +f10 (auto i) +{ + if not consteval { + (void) 0; + } else { + return id(i); + } + return i; +} + +void +g (int non_const) +{ + f1 (42); + f1 (non_const); // { dg-error "call to consteval function .f1\\(non_const\\). is not a constant expression" } +// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 } + f5 (42); + f5 (non_const); // { dg-error "call to consteval function .f5\\(non_const\\). is not a constant expression" } +// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 } + f6 (42); + f6 (non_const); + f7 (42); + f7 (non_const); + f8 (42); + f8 (non_const); + f9 (42); + f9 (non_const); + f10 (42); + f10 (non_const); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C new file mode 100644 index 00000000000..7952d495d8b --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C @@ -0,0 +1,73 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// Test unevaluated operands. + +consteval int id (int i) { return i; } + +constexpr int +f1 (auto i) +{ + // Unevaluated operand -> don't promote. + auto p = sizeof (&id); + (void) p; + return i; +} + +constexpr int +f2 (auto i) +{ + // Unevaluated operand -> don't promote. + auto p = noexcept (id); + (void) p; + return i; +} + +constexpr int +f3 (auto i) +{ + // Unevaluated operand -> don't promote. + auto p = noexcept (id (i)); + (void) p; + return i; +} + +constexpr int +f4 (auto i) +{ + // Unevaluated operand -> don't promote. + decltype(id) p; + (void) p; + return i; +} + +constexpr int +f5 (auto i) +{ + // Unevaluated operand -> don't promote. + __extension__ auto p = alignof (id (i)); + (void) p; + return i; +} + +constexpr int +f6 (auto i) requires requires { id (i); } +{ + return i; +} + +void +g (int non_const) +{ + f1 (42); + f1 (non_const); + f2 (42); + f2 (non_const); + f3 (42); + f3 (non_const); + f4 (42); + f4 (non_const); + f5 (42); + f5 (non_const); + f6 (42); + f6 (non_const); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C new file mode 100644 index 00000000000..47ec9b60b6c --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C @@ -0,0 +1,17 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// { dg-options "-fno-immediate-escalation" } + +consteval int id(int i) { return i; } + +constexpr int +f (auto i) +{ + return id (i); // { dg-error "not a constant expression" } +} + +int +g () +{ + return f (42); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C new file mode 100644 index 00000000000..a18106f8e0f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C @@ -0,0 +1,20 @@ +// P2564R3 +// { dg-do compile { target c++20 } } + +consteval int id(int i) { return i; } + +constexpr int +f (auto t) +{ + return t + id (t); +} + +constexpr int +f2 (auto t) +{ + return t + f(t); // { dg-message "immediate-escalating expression .f\\(t\\)." } +} + +int z; // { dg-message "not const" } +auto y1 = f2 (42); +auto y2 = f2 (z); // { dg-error "value of .z. is not usable in a constant expression|call to consteval function" } diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C new file mode 100644 index 00000000000..3ceb05e41f4 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C @@ -0,0 +1,7 @@ +// P2564R3 +// { dg-do compile { target c++20 } } + +consteval int g(int p) { return p; } +template constexpr auto f(T) { return g; } +int r = f(1)(2); // proposed ok +int s = f(1)(2) + r; // { dg-error "call to consteval function|returns address of immediate function" } diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C new file mode 100644 index 00000000000..eddc45b54b8 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C @@ -0,0 +1,88 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// Testcase from P2564R3. + +consteval int id(int i) { return i; } +constexpr char id(char c) { return c; } + +template +constexpr int f(T t) { + return t + id(t); // { dg-message "immediate-escalating expression .id\\(t\\)." } +} + +auto a = &f; // OK, f is not an immediate function +auto b = &f; // { dg-error "taking address of an immediate function" } + +static_assert(f(3) == 6); // OK + +template +constexpr int g(T t) { // g is not an immediate function + return t + id(42); // because id(42) is already a constant +} + +template +constexpr bool is_not(T t, F f) { + return not f(t); +} + +consteval bool is_even(int i) { return i % 2 == 0; } + +static_assert(is_not(5, is_even)); // OK + +int x = 0; + +template +constexpr T h(T t = id(x)) { // h is not an immediate function + return t; +} + +template +constexpr T hh() { // hh is an immediate function + return h(); // { dg-error "the value of .x. is not usable in a constant expression" } +// { dg-message "immediate-escalating expression .id\\(x\\)." "" { target *-*-* } .-1 } +} + +int i = hh(); // { dg-error "call to consteval function|called in a constant expression" } + // error: hh() is an immediate-escalating expression + // outside of an immediate-escalating function +struct A { + int x; + int y = id(x); +}; + +// ??? [expr.const]#example-9 says: +// k is not an immediate function because A(42) is a +// constant expression and thus not immediate-escalating +// But I think the call to id(x) is *not* a constant expression and thus +// it is an immediate-escalating expression. Therefore k *is* +// an immediate function. So we get the error below. clang++ agrees. +template +constexpr int k(int) { + return A(42).y; +} + +int +test (int i) +{ + int r = g (42) + g(i); + int t = k(42) + + k(i); // { dg-error "call to consteval function|not a constant" } + return r + t; +} + +// Just like above, but make the call to id(x) actually a constant. +struct A2 { + static constexpr int x = 42; + int y = id(x); +}; + +template +constexpr int k2(int) { + return A2(42).y; +} + +int +test2 (int i) +{ + return k2(42) + k2(i); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C new file mode 100644 index 00000000000..f181cb32942 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C @@ -0,0 +1,27 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// Cribbed from clang's cxx2b-consteval-propagate.cpp. + +consteval int id(int i) { return i; } + +template +constexpr int f(T t); + +auto a1 = &f; +auto b1 = &f; + +template +constexpr int f(T t) { + return id(0); +} + +template +constexpr int f2(T); + +auto a2 = &f2; // { dg-error "taking address" } +auto b2 = &f2; // { dg-error "taking address" } + +template +constexpr int f2(T t) { + return id(t); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C new file mode 100644 index 00000000000..3a2e09b17b0 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C @@ -0,0 +1,30 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// From clang's cxx2b-consteval-propagate.cpp. This test ICEd when I worked on +// P2564. + +consteval int f (int); + +struct S { + int a = 0; + int b = f (a); +}; + +constexpr bool +g (auto i) +{ + S s{i}; + return s.b == 2 *i; +} + +consteval int +f (int i) +{ + return 2 * i; +} + +void +test () +{ + static_assert(g(42)); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C new file mode 100644 index 00000000000..3bd1b9d1674 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C @@ -0,0 +1,27 @@ +// P2564R3 +// { dg-do compile { target c++20 } } + +consteval int f (int i) { return i; } + +struct S { + int x = f(42); +}; + +constexpr S +immediate (auto) +{ + return S{}; +} + +void +g () +{ + immediate (0); +} + +consteval void +test () +{ + constexpr S s = immediate(0); + static_assert(s.x == 42); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C new file mode 100644 index 00000000000..93ed398d9bf --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C @@ -0,0 +1,59 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// From cxx2b-consteval-propagate.cpp. + +void side_effect(); + +consteval int +f (int x) +{ + if (!x) + side_effect(); // { dg-error "call to non-.constexpr. function" } + return x; +} + +struct SS { + int y = f(1); + int x = f(0); + SS(); +}; +SS::SS(){} // { dg-error "call to consteval function" } + +consteval int +f2 (int x) +{ + if (!__builtin_is_constant_evaluated ()) + side_effect(); + return x; +} + +struct S2 { + int x = f2(0); + constexpr S2(); +}; + +constexpr S2::S2(){} +S2 s = {}; +constinit S2 s2 = {}; + +struct S3 { + int x = f2(0); + S3(); +}; +S3::S3(){} + +consteval int undef (int x); // { dg-warning "never defined" } + +struct X { + int a = sizeof(undef(0)); + int x = undef(0); + + X() = default; // { dg-error "modification of .x. is not a constant expression" } +}; + +void +test () +{ + [[maybe_unused]] X x; // { dg-error "call to consteval function" } +// { dg-message "promoted to an immediate function" "" { target *-*-* } .-1 } +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C new file mode 100644 index 00000000000..118cf576f14 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C @@ -0,0 +1,76 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// The problem here was that while parsing, we first process calling +// 'f' from 'g' but only when instantiating 'f' do we promote 'f' +// to consteval. When the var we're initializing is marked constexpr, +// store_init_value detects the problem that we're calling a consteval +// function with non-const argument. + +consteval int id(int i) { return i; } + +// Don't let the instantiations confuse us, e.g. instantiating a fn +// prior to entering 'g'. +template +constexpr int f1(T t) { return id (t); } + +template +constexpr int f2(T t) { return id (t); } + +template +constexpr int f3(T t) { return id (t); } + +template +constexpr int f4(T t) { return id (t); } + +template +constexpr int f5(T t) { return id (t); } + +template +constexpr int f6(T t) { return id (t); } + +template +constexpr int f7(T t) { return id (t); } + +template +constexpr int f8(T t) { return id (t); } + +template +constexpr int f9(T t) { return id (t); } + +template +constexpr int f10(T t) { return id (t); } + +template +constexpr int g1(T t) { auto p = id; return p (t); } + +int non_const; + +auto a1 = f1 (non_const); // { dg-error "call to consteval function|not usable" } +constexpr auto a2 = f2 (non_const); // { dg-error "not a constant|not usable" } +auto a3 = f3 (42); +constexpr auto a4 = f4 (42); + +void +g () +{ + auto a5 = f5 (non_const); // { dg-error "not a constant|not usable" } + constexpr auto a6 = f6 (non_const); // { dg-error "not usable" } + auto a7 = f7 (42); + constexpr auto a8 = f8 (42); + (void) f9 (non_const); // { dg-error "not a constant|not usable" } + (void) f10 (42); + (void) g1 (non_const); // { dg-error "not a constant|not usable" } +} + +struct S { + int y; + int x = id (y); + // Promoted to consteval. + template + constexpr S(T t) : y (id (t)) {} +}; + +S s1(1); +S s2(non_const); // { dg-error "call to consteval function|not usable" } +constexpr S s3(1); +constexpr S s4(non_const); // { dg-error "not usable" } diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C new file mode 100644 index 00000000000..080fc76f26e --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C @@ -0,0 +1,82 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// { dg-options "-Wno-c++23-extensions" } + +consteval int zero (int) +{ + return 0; +} + +struct A { + // A::A(auto) promoted to consteval. + constexpr A(auto i) { zero (i); } +}; + +// 'f1' is an immediate function because its body contains a call to an +// immediate constructor 'A' and that call is not a constant expression +constexpr void +f1 (auto i) +{ + A a{i}; +} + +// 'f2' is an immediate function because its body contains a call to an +// immediate constructor 'A' and that call is not a constant expression +constexpr void +f2 (auto i) +{ + A a{i}; +} + +void +f3 (int i) +{ + A a{i}; // { dg-error "not a constant expression" } +} + +inline void +f7 (int i) +{ + A a{i}; // { dg-error "not a constant expression" } +} + +constexpr void +f8 (int i) +{ + A a{i}; // { dg-error "not a constant expression" } +} + +/* "An expression or conversion is immediate-escalating if it is not initially + in an immediate function context" but this one is, so we do *not* promote + f4 to consteval. */ +constexpr void +f4 (auto i) +{ + if consteval { + A a{i}; + } +} + +constexpr void +f5 (auto i) +{ + if not consteval { + (void) 0; + } else { + A a{i}; + } +} + +void +f6 (int x) +{ + f1 (0); + f1 (x); // { dg-error "not a constant expression" } + f2 (0); + f2 (x); // { dg-error "not a constant expression" } + f3 (0); + f4 (x); + f4 (0); + f5 (x); + f5 (0); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C new file mode 100644 index 00000000000..9c4a23389ce --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C @@ -0,0 +1,67 @@ +// P2564R3 +// { dg-do compile { target c++20 } } + +consteval int +zero (int) +{ + return 0; +} + +constexpr int +f1 (auto i) +{ + return zero (i); +} + +constexpr int +f2 (auto i) +{ + return f1 (i); +} + +constexpr int +f3 (auto i) +{ + return f2 (i); +} + +constexpr int +f4 (auto i) +{ + return f3 (i); +} + +constexpr int +f5 (auto i) +{ + return f4 (i); +} + +constexpr int +f6 (auto) +{ + // This call is a constant expression, so don't promote f6. + return f5 (42); +} + +constexpr int +f7 (auto) +{ + // This call is a constant expression, so don't promote f7. + return zero (42); +} + +auto p1 = &f5; // { dg-error "taking address" } +static auto p2 = &f4; // { dg-error "taking address" } +auto p3 = &f6; +static auto p4 = &f6; +auto p5 = &f7; +static auto p6 = &f7; + +void +g () +{ + static auto q1 = &f4; // { dg-error "taking address" } + static auto q2 = &f6; + static auto q3 = &f7; +} diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval11.C b/gcc/testsuite/g++.dg/cpp2a/consteval11.C index 091127eabbf..f5de24bdf77 100644 --- a/gcc/testsuite/g++.dg/cpp2a/consteval11.C +++ b/gcc/testsuite/g++.dg/cpp2a/consteval11.C @@ -7,9 +7,11 @@ constexpr int a = bar (1); constexpr int b = bar (2); // { dg-message "in 'constexpr' expansion of" } constexpr int c = 0 ? bar (3) : 1; const int d = bar (4); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } const int e = 0 ? bar (5) : 1; int f = bar (1); int g = bar (6); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } int h = 0 ? bar (7) : 1; void @@ -19,25 +21,35 @@ foo () constexpr int b = bar (2); // { dg-message "in 'constexpr' expansion of" } constexpr int c = 0 ? bar (3) : 1; const int d = bar (4); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } const int e = 0 ? bar (5) : 1; int f = bar (1); int g = bar (6); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } int h = 0 ? bar (7) : 1; // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } h += 0 ? bar (8) : 1; // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } if (0) bar (9); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } else bar (10); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } if (1) bar (11); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } else bar (12); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } if constexpr (0) bar (13); else bar (14); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } if constexpr (1) bar (15); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } else bar (16); } @@ -120,18 +132,24 @@ quux () { if (0) bar ((T) 2); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } else bar ((T) 3); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } if (1) bar ((T) 4); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } else bar ((T) 5); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } if constexpr (0) bar ((T) 6); else bar ((T) 7); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } if constexpr (1) bar ((T) 8); // { dg-message "in 'constexpr' expansion of" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } else bar ((T) 9); } diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval3.C b/gcc/testsuite/g++.dg/cpp2a/consteval3.C index 9efac8c8eae..1199e9db623 100644 --- a/gcc/testsuite/g++.dg/cpp2a/consteval3.C +++ b/gcc/testsuite/g++.dg/cpp2a/consteval3.C @@ -16,8 +16,8 @@ consteval auto [ b, c ] = S (); // { dg-error "structured binding declaration c int f5 (consteval int x) { return x; } // { dg-error "a parameter cannot be declared 'consteval'" } consteval int f6 (int x) { return x; } int d = 6; // { dg-message "'int d' is not const" } -int e = f6 (d); // { dg-error "the value of 'd' is not usable in a constant expression" } -constexpr int f7 (int x) { return f6 (x); } // { dg-error "'x' is not a constant expression" } +int e = f6 (d); // { dg-error "the value of 'd' is not usable in a constant expression|call to consteval function" } +constexpr int f7 (int x) { return f6 (x); } // { dg-error "'x' is not a constant expression|call to consteval function" } constexpr int f = f7 (5); using fnptr = int (int); fnptr *g = f6; // { dg-error "taking address of an immediate function 'consteval int f6\\(int\\)'" } diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval34.C b/gcc/testsuite/g++.dg/cpp2a/consteval34.C index d5e2d1dc5d2..75d4caa622e 100644 --- a/gcc/testsuite/g++.dg/cpp2a/consteval34.C +++ b/gcc/testsuite/g++.dg/cpp2a/consteval34.C @@ -6,6 +6,7 @@ constexpr int foo (bool b) { return b ? bar (3) : 2; // { dg-message "in .constexpr. expansion" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } } static_assert (foo (false) == 2); @@ -21,13 +22,20 @@ void g () { __extension__ int a1[bar(3)]; // { dg-message "in .constexpr. expansion" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } int a2[sizeof (bar(3))]; int a3 = false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } a3 += false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } __extension__ int a4 = false ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } __extension__ int a5 = true ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } int a6 = bar (2) ? 1 : 2; // { dg-message "in .constexpr. expansion" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } int a7 = bar (2) - 1 ? 1 : 2; // { dg-message "in .constexpr. expansion" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } } diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval9.C b/gcc/testsuite/g++.dg/cpp2a/consteval9.C index aa75ba37849..a03226d17c6 100644 --- a/gcc/testsuite/g++.dg/cpp2a/consteval9.C +++ b/gcc/testsuite/g++.dg/cpp2a/consteval9.C @@ -13,6 +13,7 @@ template void qux () { int a = bar (N); // { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } } // This function is not instantiated so NDR. @@ -30,3 +31,4 @@ baz () } int a = bar (2); // { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" } +// { dg-error "call to consteval function" "" { target *-*-* } .-1 } diff --git a/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C b/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C index 16bc0b85395..fc268d44e1a 100644 --- a/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C +++ b/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C @@ -480,8 +480,8 @@ #ifndef __cpp_consteval # error "__cpp_consteval" -#elif __cpp_consteval != 201811 -# error "__cpp_consteval != 201811" +#elif __cpp_consteval != 202211L +# error "__cpp_consteval != 202211L" #endif #ifndef __cpp_concepts diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C index 33b547d2b50..ecb46b016a6 100644 --- a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C @@ -22,6 +22,6 @@ struct Z: Y int main() { X() == X(); // { dg-error "no match" } - X x; x == x; // { dg-error "x' is not usable in a constant expression" } + X x; x == x; // { dg-error "x' is not usable in a constant expression|call to consteval function" } Y() == Y(); // { dg-warning "nodiscard" } } diff --git a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc index 9d2115b3f4f..82f7cd54fba 100644 --- a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc +++ b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc @@ -52,3 +52,4 @@ test01() // { dg-prune-output "reinterpret_cast.* is not a constant expression" } // { dg-prune-output "cast from 'void.' is not allowed" } +// { dg-prune-output "not a constant expression" } diff --git a/libstdc++-v3/testsuite/std/format/string_neg.cc b/libstdc++-v3/testsuite/std/format/string_neg.cc index 7a60ef8cf0e..69bcc736cff 100644 --- a/libstdc++-v3/testsuite/std/format/string_neg.cc +++ b/libstdc++-v3/testsuite/std/format/string_neg.cc @@ -2,5 +2,5 @@ #include -auto s = std::format(" {9} "); +auto s = std::format(" {9} "); // { dg-error "call to consteval function" } // { dg-error "invalid.arg.id" "" { target *-*-* } 0 } base-commit: 5bb6a8766ec83e20550e1b59f2accb1ef25e3369 -- 2.41.0