From patchwork Thu Nov 23 16:46:29 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marek Polacek X-Patchwork-Id: 169027 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:ce62:0:b0:403:3b70:6f57 with SMTP id o2csp576957vqx; Thu, 23 Nov 2023 08:47:52 -0800 (PST) X-Google-Smtp-Source: AGHT+IHTvbpprj2BjUMSzM48B9IjpmHWiiQ7vIfdRFMI5uJl8RFWi2xqkEQ8iEOL7oZS01mu2U0d X-Received: by 2002:a0c:ead0:0:b0:679:d325:f32f with SMTP id y16-20020a0cead0000000b00679d325f32fmr22703qvp.63.1700758072382; Thu, 23 Nov 2023 08:47:52 -0800 (PST) ARC-Seal: i=2; a=rsa-sha256; t=1700758072; cv=pass; d=google.com; s=arc-20160816; b=BsLSw4zO1ZlUcT6blNa9DN7I2dM1GcPwrNvKR9k7HUmvTdlPeuPX4jJUdDyJ/oBUQ+ vJbwaMDo21woNNSaryVs17xtpS2RQg0C3+a8gu7jGbc8czjvibj++Zo4FzO5wmcNdsoK 7JhCd/aRx6A8l4hBrWeBX3qDE4TqEiRGlu8G6MSj1DzvJJPcr5fgQDn4G3cLSvBHffFN HczSVTG0wCdRWOZJyBL2CUjRgWe159++XDUxlFBaZg7N9RDDZcWuD6raIQyYcOloAEPy 6QCj+LWOU26+0T/9Usn9gFYMA6HJ+KMX9op4lKF4FCMutqa6TKQW4+qgLtrno1eJGAqC xvMw== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=errors-to:list-subscribe:list-help:list-post:list-archive :list-unsubscribe:list-id:precedence:content-disposition:user-agent :in-reply-to:mime-version:references:message-id:subject:cc:to:from :date:dkim-signature:arc-filter:dmarc-filter:delivered-to; bh=cab1M8DiNyZoqe+pHB4kJBx5OWQQgkWj1dOUHj0L1cg=; fh=rsfJLMIL/7j+58qzkgIXBlfQSah3iXypQc76SN5PqTs=; b=UBa79ll8fnZHNO2fMXKhO6EXbZU5opJ6q7nQQuE4+H2M3LNQ+ACGgG5DfHo02xVF10 2lWDODcmbHNGmzziIgSN4ZbSx2eTXxlYlya4+2Tf9L3mol71zscmq9GXcSA5mTQuxGha B0NRZJ+sjkswQ5NstpcUkIpSR6CgtnUo6c+7m9KkFGfaOSZf4aNoYHWFSfotyBp1Fj1I HkBl//kIP0JIZz6qzC7UCs8IDDv5l7p/5aEBGlCzeAzuykgZrCECpv9YRRXaytxL9bec dIP+p9Gqf12+csn7SHHaCz1tCJneAgiBXoez3aTyk21wI76s3Lo2+Ymkev9bIqMXtM9i PmHg== ARC-Authentication-Results: i=2; mx.google.com; dkim=neutral (body hash did not verify) header.i=@redhat.com header.s=mimecast20190719 header.b=f6ZxHyOM; arc=pass (i=1); spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 8.43.85.97 as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=redhat.com Received: from server2.sourceware.org (server2.sourceware.org. [8.43.85.97]) by mx.google.com with ESMTPS id s10-20020ad4500a000000b0066d197c81e5si1477354qvo.374.2023.11.23.08.47.52 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Nov 2023 08:47:52 -0800 (PST) Received-SPF: pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 8.43.85.97 as permitted sender) client-ip=8.43.85.97; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@redhat.com header.s=mimecast20190719 header.b=f6ZxHyOM; arc=pass (i=1); spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 8.43.85.97 as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=redhat.com Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id D50553870C32 for ; Thu, 23 Nov 2023 16:47:49 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id A6DA43858D33 for ; Thu, 23 Nov 2023 16:46:40 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org A6DA43858D33 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org A6DA43858D33 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1700758011; cv=none; b=ldEWTB7V1B0/otcCv2SZkiZP/rZ0rJ/b9CpIIqDWFolsT3/+HntmGGTK9Ticd1WyeJLUI3VSb4koycQuuSeeD9eBqhjP7NOqrbBnZsxlm3NdqKTpdiULaJ70B5McPBE7p2j7BSFK9ve/fNd2iJoSgxWkyaNYA3EvDaGQAjd5fEQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1700758011; c=relaxed/simple; bh=0f3yn1sbZ1TPoAZbrb9VgacVwv+sOgY9Hi8xI3FJsRg=; h=DKIM-Signature:Date:From:To:Subject:Message-ID:MIME-Version; b=OsCQVfMTKknwlLtX3GhZy4Af9RHAf8aBLvKig/yoSqzRzJcvgNFEx7YNHH1YBqakbVfVbsWJs/CQQ1NvzZkyVuAp/6IrM2nm+CLdj+ZkvgZW0Nti55ldix+tq+AwqDzTHZ8AbeDpOamRPgh4twSWGAh9XvHRbJAIPzaGaw3jKig= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1700758000; 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=Rs4Rs3+gkp+N8tCqoWQMXA/ZBvikSTSou+KzXIcU3W0=; b=f6ZxHyOMuGGWV1Gdqc4Fqlt0XGjEsh0/e6LjZBdS8MOUnrNePw9J48XFjY0sLzSix8Klxe A1UX/fVK9yZ2tdeK730Yr3XD6A0jod3ayLZCLdlCHHEEOiO4Z/wErHHG9BJnw/CjFPcULE ezaF6KMGv0TKbH6/HJU1RoxZ8p7pc7g= Received: from mail-ot1-f70.google.com (mail-ot1-f70.google.com [209.85.210.70]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-290-Dtu6glUgOJSU1EfYVT-3ng-1; Thu, 23 Nov 2023 11:46:36 -0500 X-MC-Unique: Dtu6glUgOJSU1EfYVT-3ng-1 Received: by mail-ot1-f70.google.com with SMTP id 46e09a7af769-6d646f61253so1007798a34.0 for ; Thu, 23 Nov 2023 08:46:36 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1700757995; x=1701362795; 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=cab1M8DiNyZoqe+pHB4kJBx5OWQQgkWj1dOUHj0L1cg=; b=G91hgiB+Z6BjVnYbtEfnC1dX75Pf+eTluzWpke5ZzbMxWkUdo/iiHwDplw2ugE0SGp YofvSh73+LwlrXQOFaeqX/qXmp7VbQ9S/5T6Tr1QeyxtoLMdu40gAfjIpIe4O99JRAw2 C7W1mGIF7ysgDwWz3FtWqafmAb1vhxQv7XFhy9/mh693kO5mkKgkCNgEYJ9OHXHxRL/H 0IKs4YRsDNKgq++aJWi692ghVWaKFAWmyKl0L+UmS2zSqcNBhs7wuK0rVeZLsU7ibT2R 4EDL38Z6N1g2eVdAS6Rq0CJo41DRn5Rrk863AtXpQfPZibsZjwVcNijEqpR1OMsxxxQp FLdQ== X-Gm-Message-State: AOJu0YyhPlEACaXJABEgd+vsM8VGgETuPv0C6AhEgz9eQg8JowQXpD12 YvPrONI+RWnFAYIgoo0mGhydqoGIkmEi3yewOJLsBRKEvcubzJLnS0t3ia/aRAHJvG7hYbzgQO1 TqZGCRVEyrzIReXcTAynVtWf3eQ== X-Received: by 2002:a9d:6181:0:b0:6b9:146a:f1c9 with SMTP id g1-20020a9d6181000000b006b9146af1c9mr133847otk.0.1700757993541; Thu, 23 Nov 2023 08:46:33 -0800 (PST) X-Received: by 2002:a9d:6181:0:b0:6b9:146a:f1c9 with SMTP id g1-20020a9d6181000000b006b9146af1c9mr133797otk.0.1700757992450; Thu, 23 Nov 2023 08:46:32 -0800 (PST) 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 b1-20020a0c9b01000000b00679dad5b3f7sm648294qve.84.2023.11.23.08.46.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Nov 2023 08:46:31 -0800 (PST) Date: Thu, 23 Nov 2023 11:46:29 -0500 From: Marek Polacek To: Jason Merrill Cc: GCC Patches Subject: [PATCH v5] 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> <462dd81b-cfb2-4066-bca4-403335147f5d@redhat.com> <6a3303b5-85b3-4d8f-a4e3-4f41455ec6d1@redhat.com> MIME-Version: 1.0 In-Reply-To: <6a3303b5-85b3-4d8f-a4e3-4f41455ec6d1@redhat.com> User-Agent: Mutt/2.2.9 (2022-11-12) X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Disposition: inline X-Spam-Status: No, score=-20.4 required=5.0 tests=BAYES_00, DKIM_INVALID, DKIM_SIGNED, GIT_PATCH_0, KAM_DMARC_NONE, KAM_DMARC_STATUS, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_SORBS_WEB, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE, URIBL_BLACK autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1775050664989312267 X-GMAIL-MSGID: 1783374096593911016 On Mon, Nov 13, 2023 at 09:06:09PM -0500, Jason Merrill wrote: > On 11/6/23 17:34, Marek Polacek wrote: > > On Fri, Nov 03, 2023 at 01:51:07PM -0400, Jason Merrill wrote: > > > On 11/2/23 11:28, Marek Polacek wrote: > > > > On Sat, Oct 14, 2023 at 12:56:11AM -0400, Jason Merrill wrote: > > > > > On 10/10/23 13:20, Marek Polacek wrote: > > > > > > 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. > > > > > > > > > > Yes. Or, the only immediate-escalating functions referenced have already > > > > > been checked. > > > > > > It looks like you haven't pursued this yet? One implementation thought: > > > > Oops, I'd forgotten to address that. > > > > > maybe_store_cfun... could stop skipping immediate_escalating_function_p > > > (current_function_decl), and after we're done folding if the current > > > function isn't in the hash_set we can go ahead and set > > > DECL_ESCALATION_CHECKED_P? > > > > Clever, I see what you mean. IOW, we store c_f_d iff the function contains > > an i-e expr. If not, it can't possibly become consteval. I've added that > > into cp_fold_function, and it seems to work well... > > > > ...except it revealed a different problem: cp_fold_r -> cp_fold will, since > > https://gcc.gnu.org/pipermail/gcc-patches/2016-March/443993.html, remove > > UNARY_PLUS_EXPR, leading us into this problem: > > > > // stmt = +id(i) > > cp_fold (...); > > // stmt = id(i) > > > > and the subsequent tree walk walks the CALL_EXPR's operands, so > > cp_fold_immediate_r will never see the CALL_EXPR, so we miss an i-e expr. > > > > Perhaps a better solution than the kludge I added would be to only call > > cp_fold_immediate_r after cp_fold. Or potentially before /and/ after if > > cp_fold changes the expression? > > Or walk everything with cp_fold_immediate_r before walking again with > cp_fold_r? Maybe, but I might run into complexity issues again unless I add a new pset into cp_fold_data. I did something else: only re-run cp_fold_immediate_r if cp_fold turned an expression into a CALL_EXPR. > > > > > It also seems odd that the ADDR_EXPR case calls vec_safe_push > > > > > (deferred_escalating_exprs, while the CALL_EXPR case calls > > > > > maybe_store_cfun_for_late_checking, why the different handling? > > > > > > > > maybe_store_cfun_for_late_checking saves current_function_decl > > > > so that we can check: > > > > > > > > void g (int i) { > > > > fn (i); // error if fn promotes to consteval > > > > } > > > > > > Yes, but why don't we want the same handling for ADDR_EXPR? > > > > The handling can't be exactly the same due to global vars like > > > > auto p1 = &f5; > > > > ...but it's wrong to only save the ADDR_EXPR if it's enclosed in > > a function, because the ADDR_EXPR could be inside a consteval if > > block, in which case I think we're not supposed to error. Tested > > in consteval-prop20.C. Thanks, > > And we don't need the !current_function_decl handling for CALL_EXPR? I don't think so: we should see the call when cp_fold_function gets to __static_i_and_d. I added a comment. > The only significant difference I see between &f and f() for escalation is > that the latter might be an immediate invocation. Once we've determined > that it's not, so we are in fact looking at an immediate-escalating > expression, I'd expect the promotion handling to be identical. I'm not certain if I understand the immediate invocation remark but in v5 I've tried to unify both as much as possible. > > + /* Whether cp_fold_immediate_r is looking for immediate-escalating > > + expressions. */ > > Isn't that always what it's doing? > > The uses of ff_escalating in maybe_explain_promoted_consteval and > maybe_escalate_decl_and_cfun seem to have different purposes that I'm having > trouble following. > > For the former, it seems to control returning the offending expression > rather than error_mark_node. Why don't we always do that? > > For the latter, it seems to control recursion, which seems redundant with > the recursion in that latter function itself. And the use of the flag seems > redundant with at_eof. You're absolutely correct, the code was a knotty mess. In v5, I tried to untangle it to make it something more straightforward. I still need a new ff_ flag to signal that we can return immediately after seeing an i-e expr. > > +/* 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 () > > This name could say more about escalation? Maybe > ...for_escalation_checking? > > Or, better, merge this with maybe_store_immediate_escalating_fn? I got rid of the former function. > > +/* 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) > > +{ > > + if (cp_unevaluated_operand) > > + 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 (unchecked_immediate_escalating_function_p (decl)) > > + { > > + cp_fold_data data (ff_escalating, decl); > > + cp_walk_tree (&DECL_SAVED_TREE (decl), cp_fold_immediate_r, > > + &data, nullptr); > > + DECL_ESCALATION_CHECKED_P (decl) = true; > > Why recurse both here and in cp_fold_immediate_r? Gone now. > > + } > > + > > + /* 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 cp_fold_immediate_r, > > + because DECL could have already been consteval and we'd > > + never call cp_fold_immediate_r. */ > > + && (*evalp = cxx_constant_value (call, tf_none), > > + *evalp == error_mark_node)) > > Why check this both here and immediately after the call in > cp_fold_immediate_r? This should no longer happen. > > +escalate: > > + /* Not consteval yet, but could be. Have to look deeper. */ > > + if (unchecked_immediate_escalating_function_p (decl)) > > + { > > + /* Set before the actual walk to avoid endless recursion. */ > > + DECL_ESCALATION_CHECKED_P (decl) = true; > > + cp_fold_data d (ff_escalating, data->caller ? decl : NULL_TREE); > > It seems like the "caller" field is in place of setting > current_function_decl to decl while walking its trees. Some other places > instead override current_function_decl and call cp_fold_immediate. Can we > unify on the latter pattern, and factor it into a separate function? Done -- see find_escalating_expr. > > + cp_walk_tree (&DECL_SAVED_TREE (decl), cp_fold_immediate_r, &d, 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. */ > > + if (data->caller) > > + promote_function_to_consteval (data->caller); > > + *walk_subtrees = 0; > > + return stmt; > > + } > > Why different promotion code depending on whether we're recursing? Gone now, and hopefully less unwieldy. > > +/* 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 (!deferred_escalating_exprs) > > + return; > > + > > + for (auto e : *deferred_escalating_exprs) > > + if (TREE_CODE (e) == FUNCTION_DECL) > > + { > > + if (!DECL_ESCALATION_CHECKED_P (e)) > > + { > > + temp_override cfd (current_function_decl, e); > > + cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false); > > + } > > + if (DECL_IMMEDIATE_FUNCTION_P (e)) > > + deferred_escalating_exprs->remove (e); > > + } > > +} > > + > > +/* 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 () > > +{ > > + /* This will be null for -fno-immediate-escalation. */ > > + if (!deferred_escalating_exprs) > > + return; > > + > > + for (auto ref : *deferred_escalating_exprs) > > + { > > + 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. */ > > + { > > + temp_override cfd (current_function_decl, ref); > > + cp_fold_immediate (&DECL_SAVED_TREE (ref), mce_false); > > Shouldn't we skip functions here since we just processed them in the > function above? I think so. Done. Thanks a lot. 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_ESCALATION_CHECKED_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. In v3, I removed one of the new vectors and changed the other one to a hash set. This version also contains numerous cleanups. v4 merges find_escalating_expr_r into cp_fold_immediate_r. It also adds a new optimization in cp_fold_function. v5 greatly simplifies the code. PR c++/107687 PR c++/110997 gcc/c-family/ChangeLog: * c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval. * c-opts.cc (c_common_post_options): Pre-C++20, unset flag_immediate_escalation. * c.opt (fimmediate-escalation): New option. gcc/cp/ChangeLog: * call.cc (in_immediate_context): No longer static. * constexpr.cc (cxx_eval_call_expression): Adjust assert. * cp-gimplify.cc (deferred_escalating_exprs): New vec. (remember_escalating_expr): New. (enum fold_flags): Rename ff_fold_immediate to ff_find_escalating_expr. (immediate_escalating_function_p): New. (unchecked_immediate_escalating_function_p): New. (promote_function_to_consteval): New. (maybe_promote_function_to_consteval): New. (cp_fold_immediate): Move above. Return true if any errors were emitted. (find_escalating_expr): New. (maybe_explain_promoted_consteval): 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_r): If an expression turns into a CALL_EXPR after cp_fold, call cp_fold_immediate_r on the CALL_EXPR. (cp_fold_function): Set DECL_ESCALATION_CHECKED_P if deferred_escalating_exprs does not contain current_function_decl. (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_ESCALATION_CHECKED_P): New. (immediate_invocation_p): Declare. (check_immediate_escalating_refs): Likewise. (maybe_store_immediate_escalating_fn): Likewise. (process_pending_immediate_escalating_fns): Likewise. * decl.cc (finish_function): Call maybe_store_immediate_escalating_fn. * decl2.cc (c_parse_final_cleanups): Set at_eof to 2 after all templates have been instantiated; and to 3 at the end of the function. Call process_pending_immediate_escalating_fns and check_immediate_escalating_refs. * error.cc (dump_template_bindings): Check at_eof against an updated value. * module.cc (trees_out::lang_decl_bools): Stream escalated_p. (trees_in::lang_decl_bools): Likewise. * pt.cc (push_tinst_level_loc): Set at_eof to 3, not 2. * 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-prop20.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-opts.cc | 5 + gcc/c-family/c.opt | 4 + gcc/cp/call.cc | 2 +- gcc/cp/constexpr.cc | 4 +- gcc/cp/cp-gimplify.cc | 409 +++++++++++++++--- gcc/cp/cp-tree.h | 21 +- gcc/cp/decl.cc | 5 +- gcc/cp/decl2.cc | 20 +- gcc/cp/error.cc | 2 +- gcc/cp/module.cc | 4 + gcc/cp/pt.cc | 2 +- gcc/cp/typeck.cc | 6 +- gcc/doc/invoke.texi | 34 ++ 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 | 90 ++++ gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C | 21 + 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/consteval36.C | 26 +- 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 +- 48 files changed, 1596 insertions(+), 116 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-prop20.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 base-commit: b9dbdefac626ba20222ca534b58f7e493d713b9a diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc index 56c4d637186..74c0b8de2ff 100644 --- a/gcc/c-family/c-cppbuiltin.cc +++ b/gcc/c-family/c-cppbuiltin.cc @@ -1059,7 +1059,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-opts.cc b/gcc/c-family/c-opts.cc index 10403c03bd6..d68a4548c4b 100644 --- a/gcc/c-family/c-opts.cc +++ b/gcc/c-family/c-opts.cc @@ -1138,6 +1138,11 @@ c_common_post_options (const char **pfilename) if (cxx_dialect >= cxx20 || flag_concepts_ts) flag_concepts = 1; + /* -fimmediate-escalation has no effect when immediate functions are not + supported. */ + if (flag_immediate_escalation && cxx_dialect < cxx20) + flag_immediate_escalation = 0; + if (num_in_fnames > 1) error ("too many filenames given; type %<%s %s%> for usage", progname, "--help"); diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 3848f378de1..c0c323a5f17 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1890,6 +1890,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 81b104f4b40..26aa19a2415 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -9744,7 +9744,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 344107d494b..078dc875c14 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -3128,11 +3128,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, /* OK */; else if (!DECL_SAVED_TREE (fun)) { - /* When at_eof >= 2, cgraph has started throwing away + /* When at_eof >= 3, cgraph has started throwing away DECL_SAVED_TREE, so fail quietly. FIXME we get here because of late code generation for VEC_INIT_EXPR, which needs to be completely reconsidered. */ - gcc_assert (at_eof >= 2 && ctx->quiet); + gcc_assert (at_eof >= 3 && ctx->quiet); *non_constant_p = true; } else if (tree copy = get_fundef_copy (new_call.fundef)) diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc index 795c811471d..97fa317ea32 100644 --- a/gcc/cp/cp-gimplify.cc +++ b/gcc/cp/cp-gimplify.cc @@ -43,6 +43,21 @@ along with GCC; see the file COPYING3. If not see #include "omp-general.h" #include "opts.h" +/* 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, + and immediate-escalating functions that may become consteval. */ +static GTY(()) hash_set *deferred_escalating_exprs; + +static void +remember_escalating_expr (tree t) +{ + if (!deferred_escalating_exprs) + deferred_escalating_exprs = hash_set::create_ggc (37); + deferred_escalating_exprs->add (t); +} + /* Flags for cp_fold and cp_fold_r. */ enum fold_flags { @@ -53,8 +68,9 @@ enum fold_flags { definitely not in a manifestly constant-evaluated context. */ ff_mce_false = 1 << 1, - /* Whether we're being called from cp_fold_immediate. */ - ff_fold_immediate = 1 << 2, + /* Whether we are looking for an immediate-escalating expression and + can return early. */ + ff_find_escalating_expr = 1 << 2, }; using fold_flags_t = int; @@ -72,6 +88,7 @@ static tree cp_genericize_r (tree *, int *, void *); static tree cp_fold_r (tree *, int *, void *); static void cp_genericize_tree (tree*, bool); static tree cp_fold (tree, fold_flags_t); +static tree cp_fold_immediate_r (tree *, int *, void *); /* Genericize a TRY_BLOCK. */ @@ -428,6 +445,129 @@ 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); + + if (DECL_IMMEDIATE_FUNCTION_P (fn)) + return false; + + /* An immediate-escalating function is + -- the call operator of a lambda that is not declared with the consteval + specifier */ + if (LAMBDA_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)) + return true; + /* -- a function that results from the instantiation of a templated entity + defined with the constexpr specifier. */ + return is_instantiation_of_constexpr (fn); +} + +/* Return true if FN is an immediate-escalating function that has not been + checked for escalating expressions.. */ + +static bool +unchecked_immediate_escalating_function_p (tree fn) +{ + return (immediate_escalating_function_p (fn) + && !DECL_ESCALATION_CHECKED_P (fn)); +} + +/* Promote FN to an immediate function, including its clones. */ + +static void +promote_function_to_consteval (tree fn) +{ + SET_DECL_IMMEDIATE_FUNCTION_P (fn); + DECL_ESCALATION_CHECKED_P (fn) = true; + tree clone; + FOR_EACH_CLONE (clone, fn) + { + SET_DECL_IMMEDIATE_FUNCTION_P (clone); + DECL_ESCALATION_CHECKED_P (clone) = true; + } +} + +/* 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 (unchecked_immediate_escalating_function_p (fn)) + { + promote_function_to_consteval (fn); + return true; + } + + return false; +} + +/* 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. */ + +bool +cp_fold_immediate (tree *tp, mce_value manifestly_const_eval, + tree decl /*= current_function_decl*/) +{ + if (cxx_dialect <= cxx17) + return false; + + temp_override cfd (current_function_decl, decl); + + fold_flags_t flags = ff_none; + if (manifestly_const_eval == mce_false) + flags |= ff_mce_false; + + cp_fold_data data (flags); + int save_errorcount = errorcount; + tree r = cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data); + return r != NULL_TREE || errorcount > save_errorcount; +} + +/* Look for an immediate-escalating expression in TP and perhaps promote + current_function_decl to consteval. */ + +static tree +find_escalating_expr (tree *tp, tree decl) +{ + temp_override cfd (current_function_decl, decl); + cp_fold_data data (ff_find_escalating_expr); + return cp_walk_tree (tp, cp_fold_immediate_r, &data, nullptr); +} + +/* 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_ESCALATION_CHECKED_P (fn)) + { + /* See if we can figure out what made the function consteval. */ + tree x = find_escalating_expr (&DECL_SAVED_TREE (fn), NULL_TREE); + 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. */ @@ -746,7 +886,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,10 +913,12 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p) default: break; } - else if (decl - && fndecl_built_in_p (decl, BUILT_IN_CLZG, BUILT_IN_CTZG)) + else if (fndecl_built_in_p (decl, BUILT_IN_CLZG, BUILT_IN_CTZG)) ret = (enum gimplify_status) c_gimplify_expr (expr_p, pre_p, post_p); + else + /* All consteval functions should have been processed by now. */ + gcc_checking_assert (!immediate_invocation_p (decl)); } break; @@ -1035,6 +1179,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 @@ -1045,90 +1203,142 @@ 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) || cp_unevaluated_operand) { *walk_subtrees = 0; return NULL_TREE; } - switch (TREE_CODE (stmt)) - { - 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; + tree decl = NULL_TREE; + bool call_p = false; - /* Expand immediate invocations. */ + /* We are looking for &fn or fn(). */ + switch (code) + { case CALL_EXPR: case AGGR_INIT_EXPR: 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; - } + decl = cp_get_fndecl_from_callee (fn, /*fold*/false); + call_p = true; + break; + case PTRMEM_CST: + decl = PTRMEM_CST_MEMBER (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)); - } - return error_mark_node; - } + if (!ADDR_EXPR_DENOTES_CALL_P (stmt)) + decl = TREE_OPERAND (stmt, 0); break; - default: - break; + return NULL_TREE; } - return NULL_TREE; -} + if (!decl || TREE_CODE (decl) != FUNCTION_DECL) + return NULL_TREE; -/* 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. */ + tree e = NULL_TREE; -bool -cp_fold_immediate (tree *tp, mce_value manifestly_const_eval) -{ - if (cxx_dialect <= cxx17) - return false; + /* Fully escalate once all templates have been instantiated. */ + if (at_eof > 1) + { + /* 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 (unchecked_immediate_escalating_function_p (decl)) + { + /* Set before the actual walk to avoid endless recursion. */ + DECL_ESCALATION_CHECKED_P (decl) = true; + /* We're only looking for the first escalating expression; + let us not walk more trees than necessary. */ + find_escalating_expr (&DECL_SAVED_TREE (decl), decl); + } - fold_flags_t flags = ff_fold_immediate; - if (manifestly_const_eval == mce_false) - flags |= ff_mce_false; + /* In turn, maybe promote the function we find ourselves in... */ + if ((data->flags & ff_find_escalating_expr) + && 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. */ + && (e = cxx_constant_value (stmt, tf_none), e == error_mark_node)) + { + /* Since we had to set DECL_ESCALATION_CHECKED_P before the walk, + we call promote_function_to_consteval directly which doesn't + check unchecked_immediate_escalating_function_p. */ + if (current_function_decl) + promote_function_to_consteval (current_function_decl); + *walk_subtrees = 0; + return stmt; + } + } - cp_fold_data data (flags); - return !!cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data); + /* [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 (decl)) + { + e = e ? e : cxx_constant_value (stmt, tf_none); + if (e == error_mark_node) + { + if (maybe_promote_function_to_consteval (current_function_decl)) + return NULL_TREE; + if (complain & tf_error) + { + if (call_p) + { + 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, decl); + } + else if (!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. */ + return NULL_TREE; + } + return stmt; + } + /* We've evaluated the consteval function call. */ + if (call_p) + *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 (unchecked_immediate_escalating_function_p (decl)) + { + /* Make sure we're not inserting new elements while walking + the deferred_escalating_exprs hash table; if we are, it's + likely that a function wasn't properly marked checked for + i-e expressions. */ + gcc_checking_assert (at_eof <= 1); + if (current_function_decl) + remember_escalating_expr (current_function_decl); + /* auto p = &f; in the global scope won't be ensconced in + a function we could store for later at this point. (If there's + no c_f_d at this point and we're dealing with a call, we should + see the call when cp_fold_function __static_i_and_d.) */ + else if (!call_p) + remember_escalating_expr (stmt); + } + + return NULL_TREE; } /* Perform any pre-gimplification folding of C++ front end trees to @@ -1178,11 +1388,19 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_) *walk_subtrees = 0; /* Don't return yet, still need the cp_fold below. */ } - cp_fold_immediate_r (stmt_p, walk_subtrees, data); + else + cp_fold_immediate_r (stmt_p, walk_subtrees, data); } *stmt_p = stmt = cp_fold (*stmt_p, data->flags); + /* For certain trees, like +foo(), the cp_fold below will remove the +, + and the subsequent tree walk would go straight down to the CALL_EXPR's + operands, meaning that cp_fold_immediate_r would never see the + CALL_EXPR. Ew :(. */ + if (TREE_CODE (stmt) == CALL_EXPR && code != CALL_EXPR) + cp_fold_immediate_r (stmt_p, walk_subtrees, data); + if (data->pset.add (stmt)) { /* Don't walk subtrees of stmts we've already walked once, otherwise @@ -1304,6 +1522,63 @@ cp_fold_function (tree fndecl) pass ff_mce_false. */ cp_fold_data data (ff_genericize | ff_mce_false); cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &data, NULL); + + /* This is merely an optimization: if FNDECL has no i-e expressions, + we'll not save c_f_d, and we can safely say that FNDECL will not + be promoted to consteval. */ + if (deferred_escalating_exprs + && !deferred_escalating_exprs->contains (current_function_decl)) + DECL_ESCALATION_CHECKED_P (fndecl) = true; +} + +/* 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 (unchecked_immediate_escalating_function_p (fn)) + remember_escalating_expr (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 (!deferred_escalating_exprs) + return; + + for (auto e : *deferred_escalating_exprs) + if (TREE_CODE (e) == FUNCTION_DECL && !DECL_ESCALATION_CHECKED_P (e)) + cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false, e); +} + +/* 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 () +{ + /* This will be null for -fno-immediate-escalation. */ + if (!deferred_escalating_exprs) + return; + + for (auto ref : *deferred_escalating_exprs) + { + if (TREE_CODE (ref) == FUNCTION_DECL) + continue; + 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); + } + + deferred_escalating_exprs = nullptr; } /* Turn SPACESHIP_EXPR EXPR into GENERIC. */ diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 7b0b7c6a17e..2cb05b8f8ea 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -2947,8 +2947,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. */ @@ -3400,6 +3401,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_ESCALATION_CHECKED_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 @@ -5877,7 +5886,8 @@ extern GTY(()) vec *keyed_classes; /* Nonzero if we're done parsing and into end-of-file activities. - Two if we're done with front-end processing. */ + 2 if all templates have been instantiated. + 3 if we're done with front-end processing. */ extern int at_eof; @@ -6769,6 +6779,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); @@ -8411,7 +8422,11 @@ 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, + tree = current_function_decl); +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 e7701afdefd..d820b8971a9 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -18351,7 +18351,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 9e666e5eece..f9187b150c7 100644 --- a/gcc/cp/decl2.cc +++ b/gcc/cp/decl2.cc @@ -169,7 +169,9 @@ typedef hash_map= 2) + if (at_eof >= 3) return; FOR_EACH_VEC_SAFE_ELT (typenames, i, t) diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index 4f5b6e2747a..eeec6362c8d 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -5683,6 +5683,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); + WB (lang->u.fn.escalated_p); + /* We do not stream lang->u.fn.implicit_constexpr. */ goto lds_min; case lds_decomp: /* lang_decl_decomp. */ @@ -5751,6 +5753,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); + RB (lang->u.fn.escalated_p); + /* We do not stream lang->u.fn.implicit_constexpr. */ goto lds_min; case lds_decomp: /* lang_decl_decomp. */ diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index 092e6fdfd36..2d4d850d8e5 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -11107,7 +11107,7 @@ push_tinst_level_loc (tree tldcl, tree targs, location_t loc) if (tinst_depth >= max_tinst_depth) { /* Tell error.cc not to try to instantiate any templates. */ - at_eof = 2; + at_eof = 3; fatal_error (input_location, "template instantiation depth exceeds maximum of %d" " (use %<-ftemplate-depth=%> to increase the maximum)", diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc index e995fb6ddd7..00db295dced 100644 --- a/gcc/cp/typeck.cc +++ b/gcc/cp/typeck.cc @@ -7263,11 +7263,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 54c42c9659c..c639d24b36b 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 @@ -3384,6 +3385,39 @@ 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. For example: + +@example +consteval int id(int i) @{ return i; @} + +constexpr int f(auto t) +@{ + return t + id(t); // id causes f to be promoted to consteval +@} + +void g(int i) +@{ + f (3); +@} +@end example + +compiles in C++20: @code{f} is an immediate-escalating function (due to +the @code{auto} it is a function template and is declared @code{constexpr}) +and @code{id(t)} is an immediate-escalating expression, so @code{f} is +promoted to @code{consteval}. Consequently, the call to @code{id(t)} +is in an immediate context, so doesn't have to produce a constant (that +is the mechanism allowing consteval function composition). However, +with @option{-fno-immediate-escalation}, @code{f} is not promoted to +@code{consteval}, and since the call to consteval function @code{id(t)} +is not a constant expression, the compiler rejects the code. + +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 80e8ef680d9..bdedc62c30f 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 46eed13446d..ca923519f98 100644 --- a/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C @@ -20,10 +20,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..30129a4a266 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C @@ -0,0 +1,90 @@ +// 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 +// In the evaluation of A(42), the member x has just been initialized +// to constant 42. And A(42) is constant-evaluated because "An aggregate +// initialization is an immediate invocation if it evaluates a default +// member initializer that has a subexpression that is an +// immediate-escalating expression." +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-bogus "call to|constant" "" { xfail *-*-* } } + 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-prop20.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C new file mode 100644 index 00000000000..f1bb08e2dba --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C @@ -0,0 +1,21 @@ +// P2564R3 +// { dg-do compile { target c++20 } } +// { dg-options "-Wno-c++23-extensions" } + +consteval int id(int i) { return i; } + +constexpr int +f (auto i) +{ + return id (i); +} + +void +g () +{ + auto p = &f; // { dg-error "taking address" } + decltype(&f) x; + if consteval { + auto q = &f; + } +} 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 05cecea4502..c2ee3c7a82a 100644 --- a/gcc/testsuite/g++.dg/cpp2a/consteval11.C +++ b/gcc/testsuite/g++.dg/cpp2a/consteval11.C @@ -8,9 +8,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 @@ -20,25 +22,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); } @@ -121,18 +133,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 068827ba516..7562f403f74 100644 --- a/gcc/testsuite/g++.dg/cpp2a/consteval34.C +++ b/gcc/testsuite/g++.dg/cpp2a/consteval34.C @@ -7,6 +7,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); @@ -22,13 +23,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/consteval36.C b/gcc/testsuite/g++.dg/cpp2a/consteval36.C index 9c470e4b7d7..8e27f2e33c6 100644 --- a/gcc/testsuite/g++.dg/cpp2a/consteval36.C +++ b/gcc/testsuite/g++.dg/cpp2a/consteval36.C @@ -6,17 +6,17 @@ consteval int id (int i) { return i; } void g (int i) { - 1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" } - 1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" } - 1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" } - 1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "'i' is not a constant expression" } - 1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "'i' is not a constant expression" } - 1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "'i' is not a constant expression" } - 1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "'i' is not a constant expression" } - 1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "'i' is not a constant expression" } - 1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "'i' is not a constant expression" } - id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" } - 1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" } - 1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" } - 1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" } + 1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" } + 1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" } + 1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" } + 1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" } + 1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" } + 1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" } + 1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "call to consteval function|'i' is not a constant expression" } + 1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" } + 1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" } + id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" } + 1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" } + 1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" } + 1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" } } diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval9.C b/gcc/testsuite/g++.dg/cpp2a/consteval9.C index 051a3d4e355..ad882d51c9b 100644 --- a/gcc/testsuite/g++.dg/cpp2a/consteval9.C +++ b/gcc/testsuite/g++.dg/cpp2a/consteval9.C @@ -14,6 +14,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. @@ -31,3 +32,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 }