From patchwork Mon Jun 17 12:15:23 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Iain Sandoe X-Patchwork-Id: 1948613 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20230601 header.b=fviO+r8N; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4W2pkl0xpXz20KL for ; Mon, 17 Jun 2024 22:15:59 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 67EA4386103B for ; Mon, 17 Jun 2024 12:15:57 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from mail-wm1-x333.google.com (mail-wm1-x333.google.com [IPv6:2a00:1450:4864:20::333]) by sourceware.org (Postfix) with ESMTPS id C65CE385C6CD for ; Mon, 17 Jun 2024 12:15:26 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org C65CE385C6CD Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org C65CE385C6CD Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2a00:1450:4864:20::333 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1718626531; cv=none; b=WEwTO1nexYK+gLmD8Tq04fxYGqzBwQs7F8yQNJc423fez7W5Uou69rCy9WtYgMEEWbqUFxGiKaM2q02tagY/E1QHFMuHszFef7UpMamIy0BFuhu8o6cyJC71BiCccANc0hZ/Ogv0w4P5ZQYTcUi6NG/FO7uSSSRvKefIQW3gT50= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1718626531; c=relaxed/simple; bh=3AvoQL59/L7MxuLktAGoiLCDR4Yzp44iLsS8qWkB0ME=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=DyAEOsTXJVCZKHyJO5u6k/56B7sGOfT2TytB/v88527iG5NjoYEbIw6CEY9mmwGPJqVkIevbzVqviNMBGkKO+2TcT+r0KrOaiURtB4Vub2jbEbMxtsHNgqmCljEFE2v/pHqFfdx1/5BYeDLEePWJWbkLrSuk+afB6veujvpWPRk= ARC-Authentication-Results: i=1; server2.sourceware.org Received: by mail-wm1-x333.google.com with SMTP id 5b1f17b1804b1-421b9068274so38143435e9.1 for ; Mon, 17 Jun 2024 05:15:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1718626525; x=1719231325; darn=gcc.gnu.org; h=content-transfer-encoding:mime-version:reply-to:message-id:date :subject:to:from:from:to:cc:subject:date:message-id:reply-to; bh=aNRniidVinyjD/b5RrFAwsIUPNtWxi0uBQT/o2HPC8w=; b=fviO+r8NMxL2TiF2K1OvumjVvGWdKue72LB3LCEySw1cm5YFq+Nb8vhynECK0e+Reo 7bOlZXchYKDO9z5vJM0/EoeqjgPm1cxQcxMFy6JUSWBbgHbDP0FxmguxsknSGy+thI2h g8jXgM3OalzqoHNH0Dj1isWWqmEsi7XswTj258nfce5FEH1wBMvVYZJgM/E7QY15JLLG 5EUbfPD+vjwbj6RxzJFrWp9g8oTp/R5ZzRPZJW7w0K434KBHXChG1K6G0OYZ12Y43ABD AJDE/53Jyb0hbB76MGr0qt450zDAl7spXX2nKRBprNWZcUNQieJ+kpe/yeuSEJUIPxx7 7N5A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1718626525; x=1719231325; h=content-transfer-encoding:mime-version:reply-to:message-id:date :subject:to:from:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=aNRniidVinyjD/b5RrFAwsIUPNtWxi0uBQT/o2HPC8w=; b=SnDpIGNuv6oc9KU3EvshmEKZiw0keBvjcGYEAD9bIyu5IkLcyjvn7n7l8RYb0wMNTZ wWR3DcPvxilxxMhg61raWh1u/PxSlYWLn0LvAPr+tjkDLlA1vZad5hm0QuSd1dnkc3ZC AhSjqJAMJ87JBfOfr7zvPJVfXfLLgAKLH0bwBTlMEDCq9SedMet9zwvQgOczXOzddPUQ 0yYno9S6s0IlGKlTnd7cApFHcxHkqPrqFxFB2Y70xtqa4qYe5gW2+tbdJ3+PefRbMo6g Tv1tUgpvc8I3lwL6qqF3rTicme/LCRx7pmyfdSAz0FU3LT7ZHB9PiO5DWB5WYSc74HWF JRpA== X-Gm-Message-State: AOJu0Yx9Pu+cWO2+lRKFOZBVqiDpXwD+1EDyVoMLmkuSyH7IcOQizL9T /cK7n8qEImIPCHyfIaEgNutF/KTqtV075DeUfh3T2/yIblT4NG4c/xV0OQ== X-Google-Smtp-Source: AGHT+IEecmfFMAaJZEEEUpJ5aZ5QlQN+IXSmx4Mowb4Uq7/aA+VI94hZ/sy1lufDCvP1gAmV0ByK9Q== X-Received: by 2002:a05:600c:3109:b0:422:35:d19d with SMTP id 5b1f17b1804b1-42304858126mr76037495e9.36.1718626524837; Mon, 17 Jun 2024 05:15:24 -0700 (PDT) Received: from localhost.localdomain (host81-138-1-83.in-addr.btopenworld.com. [81.138.1.83]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42286eef70csm196832475e9.3.2024.06.17.05.15.24 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 17 Jun 2024 05:15:24 -0700 (PDT) From: Iain Sandoe X-Google-Original-From: Iain Sandoe To: gcc-patches@gcc.gnu.org, jason@redhat.com Subject: [PATCH] c++, coroutines, contracts: Handle coroutine and void functions [PR110871, PR110872, PR115434]. Date: Mon, 17 Jun 2024 13:15:23 +0100 Message-Id: <20240617121523.78670-1-iain@sandoe.co.uk> X-Mailer: git-send-email 2.39.2 (Apple Git-143) MIME-Version: 1.0 X-Spam-Status: No, score=-8.4 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_FROM, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: iain@sandoe.co.uk Errors-To: gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org This patch came out of a discussion on Mattermost about how to deal with contracts/coroutines integration. Actually, it would also allow some semantic checking to be deferred until the same spot - at which time there are no dependent types, which can simplify the process. NOTE: this is a fix for bugs in the existing '2a' contracts impl. it does not attempt to make any of the changes required by P2900 to either code-gen or constexpr handling. Tested on x86_64-darwin, so far, OK for trunk if testing succeeds on x86_64/powerpc64 linux too? thanks, Iain --- 8< --- The current implementation of contracts emits the checks into function bodies in three places; for pre-conditions at the start of the body, for asserts in-line in the function body and for post-conditions as an addition to return statements. In general (at least with existing "2a" contract semantics) the in-line contract asserts behave as expected. However, the mechanism is not applicable to: * Handling pre conditions in coroutines since, for those, the standard specifies a wrapping of the original function body by functionality implementing initial and final suspends (along with some housekeeping to route exceptions). Thus for such transformed function bodies, the preconditions then get actioned after the initial suspend, which does not behave as intended. * Handling post conditions in functions that do not have return statements (which applies to coroutines and void functions). In the following, we identify a potentially transformed function body (in the case of coroutines, this is usually called the "ramp()" function). The patch here re-implements the code insertion in one of the two following ways (code for exposition only): * For functions with no post-conditions we wrap the potentially transformed function as follows: { handle_pre_condition_checking (); potentially_transformed_function_body (); } This implements the intent that the preconditions are processed after the function parameters are initialised but before any other actions. * For functions with post-conditions: try { if (preconditions_exist) handle_pre_condition_checking (); potentially_transformed_function_body (); } finally { handle_post_condition_checking (); } else [only if the function is not marked noexcept(true) ] { __rethrow (); } In this, post-conditions [that might apply to the return value etc.] are evaluated on every non-exceptional edge out of the function. At present, the model here is that exceptions thrown by the function propagate upwards as if there were no contracts present. If the desired semantic becomes that an exception is counted as equivalent to a contract violation - then we can add a second handler in place of the rethrow. At constexpr time we need to evaluate the contract conditions, but not the exceptional path, which is handled by a flag on the EH_ELSE_EXPR that indicates it is in use for contract handling. This patch specifically does not address changes to code-gen and constexpr handling that are contained in P2900. PR c++/115434 PR c++/110871 PR c++/110872 gcc/cp/ChangeLog: * constexpr.cc (cxx_eval_constant_expression): Handle EH_ELSE_EXPR. * contracts.cc (finish_contract_attribute): Remove excess line. (build_contract_condition_function): Post condition handlers are void now. (emit_postconditions_cleanup): Remove. (emit_postconditions): New. (add_pre_condition_fn_call): New. (add_post_condition_fn_call): New. (apply_preconditions): New. (apply_postconditions): New. (maybe_apply_function_contracts): New. (apply_postcondition_to_return): Remove. * contracts.h (apply_postcondition_to_return): Remove. (maybe_apply_function_contracts): Add. * coroutines.cc (coro_build_actor_or_destroy_function): Do not copy contracts to coroutine helpers. * cp-tree.h (CONTRACT_EH_ELSE_P): New. * decl.cc (finish_function): Handle wrapping a possibly transformed function body in contract checks. * typeck.cc (check_return_expr): Remove handling of post conditions on return expressions. gcc/ChangeLog: * gimplify.cc (struct gimplify_ctx): Add a flag to show we are expending a handler. (gimplify_expr): When we are expanding a handler, and the body transforms might have re-written DECL_RESULT into a gimple var, ensure that hander references to DECL_RESULT are also re-written to refer to the gimple var. gcc/testsuite/ChangeLog: * g++.dg/contracts/pr115434.C: New test. * g++.dg/coroutines/pr110871.C: New test. * g++.dg/coroutines/pr110872.C: New test. Signed-off-by: Iain Sandoe --- gcc/cp/constexpr.cc | 16 ++ gcc/cp/contracts.cc | 249 ++++++++++++--------- gcc/cp/contracts.h | 3 +- gcc/cp/coroutines.cc | 2 + gcc/cp/cp-tree.h | 3 + gcc/cp/decl.cc | 23 +- gcc/cp/typeck.cc | 13 +- gcc/gimplify.cc | 13 +- gcc/testsuite/g++.dg/contracts/pr115434.C | 16 ++ gcc/testsuite/g++.dg/coroutines/pr110871.C | 62 +++++ gcc/testsuite/g++.dg/coroutines/pr110872.C | 49 ++++ 11 files changed, 319 insertions(+), 130 deletions(-) create mode 100644 gcc/testsuite/g++.dg/contracts/pr115434.C create mode 100644 gcc/testsuite/g++.dg/coroutines/pr110871.C create mode 100644 gcc/testsuite/g++.dg/coroutines/pr110872.C diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index bd72533491e..9b66b1753ad 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -7808,6 +7808,22 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, non_constant_p, overflow_p); break; + case EH_ELSE_EXPR: + /* Evaluate any cleanup that applies to non-EH exits, this only for + the output of the diagnostics ??? what is really meant to happen + at constexpr-time?... */ + cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0), vc_discard, + non_constant_p, overflow_p); + + /* The presence of a contract should not affect the constexpr. */ + if (CONTRACT_EH_ELSE_P (t)) + break; + if (!*non_constant_p) + /* Also evaluate the EH handler. */ + cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 1), vc_discard, + non_constant_p, overflow_p); + break; + case CLEANUP_STMT: r = cxx_eval_constant_expression (ctx, CLEANUP_BODY (t), lval, non_constant_p, overflow_p, diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc index 0822624a910..064f0b6a9d8 100644 --- a/gcc/cp/contracts.cc +++ b/gcc/cp/contracts.cc @@ -88,64 +88,7 @@ along with GCC; see the file COPYING3. If not see return -v; } - The original decl is left alone and instead calls are generated to pre/post - functions within the body: - - void fun.pre(int v) - { - [[ assert: v > 0 ]]; - } - int fun.post(int v, int __r) - { - [[ assert: __r < 0 ]]; - return __r; - } - int fun(int v) - { - fun.pre(v); - return fun.post(v, -v); - } - - If fun returns in memory, the return value is not passed through the post - function; instead, the return object is initialized directly and then passed - to the post function by invisible reference. - - This sides steps a number of issues with having to rewrite the bodies or - rewrite the parsed conditions as the parameters to the original function - changes (as happens during redeclaration). The ultimate goal is to get - something that optimizes well along the lines of - - int fun(int v) - { - [[ assert: v > 0 ]]; - auto &&__r = -v; - goto out; - out: - [[ assert: __r < 0 ]]; - return __r; - } - - With the idea being that multiple return statements could collapse the - function epilogue after inlining the pre/post functions. clang is able - to collapse common function epilogues, while gcc needs -O3 -Os combined. - - Directly laying the pre contracts down in the function body doesn't have - many issues. The post contracts may need to be repeated multiple times, once - for each return, or a goto epilogue would need to be generated. - For this initial implementation, generating function calls and letting - later optimizations decide whether to inline and duplicate the actual - checks or whether to collapse the shared epilogue was chosen. - - For cdtors a post contract is implemented using a CLEANUP_STMT. - - FIXME the compiler already shores cleanup code on multiple exit paths, so - this outlining seems unnecessary if we represent the postcondition as a - cleanup for all functions. - - More helpful for optimization might be to make the contracts a wrapper - function (for non-variadic functions), that could be inlined into a - caller while preserving the call to the actual function? Either that or - mirror a never-continue post contract with an assume in the caller. */ + TODO: reiterate the revised implementation. */ #include "config.h" #include "system.h" @@ -801,7 +744,6 @@ finish_contract_attribute (tree identifier, tree contract) tree attribute = build_tree_list (build_tree_list (NULL_TREE, identifier), build_tree_list (NULL_TREE, contract)); - /* Mark the attribute as dependent if the condition is dependent. TODO: I'm not sure this is strictly necessary. It's going to be marked as @@ -1444,10 +1386,8 @@ build_contract_condition_function (tree fndecl, bool pre) *last = build_tree_list (NULL_TREE, value_type); TREE_CHAIN (*last) = void_list_node; - if (aggregate_value_p (value_type, fndecl)) - /* If FNDECL returns in memory, don't return the value from the - postcondition. */ - value_type = void_type_node; + /* The handler is a void return. */ + value_type = void_type_node; } TREE_TYPE (fn) = build_function_type (value_type, arg_types); @@ -1882,15 +1822,12 @@ emit_preconditions (tree attr) return emit_contract_conditions (attr, PRECONDITION_STMT); } -/* Emit statements for postcondition attributes. */ +/* Emit statements for precondition attributes. */ static void -emit_postconditions_cleanup (tree contracts) +emit_postconditions (tree attr) { - tree stmts = push_stmt_list (); - emit_contract_conditions (contracts, POSTCONDITION_STMT); - stmts = pop_stmt_list (stmts); - push_cleanup (NULL_TREE, stmts, /*eh_only*/false); + return emit_contract_conditions (attr, POSTCONDITION_STMT); } /* We're compiling the pre/postcondition function CONDFN; remap any FN @@ -1993,32 +1930,152 @@ start_function_contracts (tree decl1) if (!handle_contracts_p (decl1)) return; + /* For cdtors, we evaluate the contracts check inline. */ if (!outline_contracts_p (decl1)) - { - emit_preconditions (DECL_CONTRACTS (current_function_decl)); - emit_postconditions_cleanup (DECL_CONTRACTS (current_function_decl)); - return; - } + return; /* Contracts may have just been added without a chance to parse them, though we still need the PRE_FN available to generate a call to it. */ if (!DECL_PRE_FN (decl1)) build_contract_function_decls (decl1); +} + +/* If we have a precondition function and it's valid, call it. */ + +static void +add_pre_condition_fn_call (tree fndecl) +{ /* If we're starting a guarded function with valid contracts, we need to insert a call to the pre function. */ - if (DECL_PRE_FN (decl1) - && DECL_PRE_FN (decl1) != error_mark_node) + gcc_checking_assert (DECL_PRE_FN (fndecl) + && DECL_PRE_FN (fndecl) != error_mark_node); + + releasing_vec args = build_arg_list (fndecl); + tree call = build_call_a (DECL_PRE_FN (fndecl), args->length (), + args->address ()); + CALL_FROM_THUNK_P (call) = true; + finish_expr_stmt (call); +} + +/* Build and add a call to the post-condition checking function, when that + is in use. */ + +static void +add_post_condition_fn_call (tree fndecl) +{ + gcc_checking_assert (DECL_POST_FN (fndecl) + && DECL_POST_FN (fndecl) != error_mark_node); + + releasing_vec args = build_arg_list (fndecl); + if (get_postcondition_result_parameter (fndecl)) + vec_safe_push (args, DECL_RESULT (fndecl)); + tree call = build_call_a (DECL_POST_FN (fndecl), args->length (), + args->address ()); + CALL_FROM_THUNK_P (call) = true; + finish_expr_stmt (call); +} + +/* Add a call or a direct evaluation of the pre checks. */ + +static void +apply_preconditions (tree fndecl) +{ + if (outline_contracts_p (fndecl)) + add_pre_condition_fn_call (fndecl); + else + emit_preconditions (DECL_CONTRACTS (fndecl)); +} + +/* Add a call or a direct evaluation of the post checks. */ + +static void +apply_postconditions (tree fndecl) +{ + if (outline_contracts_p (fndecl)) + add_post_condition_fn_call (fndecl); + else + emit_postconditions (DECL_CONTRACTS (fndecl)); +} + +/* Add contract handling to the function in FNDECL. + + When we have only pre-conditions, this simply prepends a call (or a direct + evaluation, for cdtors) to the existing function body. + + When we have post conditions we build a try-finally block. + If the function might throw then the handler in the try-finally is an + EH_ELSE expression, where the post condition check is applied to the + non-exceptional path, and a rethrow is applied to the EH path. If the + function has a non-throwing eh spec, then the handler is simply the post + contract checker (either a call or, for cdtors, a direct evaluation). */ + +void +maybe_apply_function_contracts (tree fndecl) +{ + if (!handle_contracts_p (fndecl)) + /* We did nothing and the original function body statement list will be + popped by our caller. */ + return; + + bool do_pre = has_active_preconditions (fndecl); + bool do_post = has_active_postconditions (fndecl); + /* We should not have reached here with nothing to do... */ + gcc_checking_assert (do_pre || do_post); + + /* This copies the approach used for function try blocks. */ + tree fnbody = pop_stmt_list (DECL_SAVED_TREE (fndecl)); + DECL_SAVED_TREE (fndecl) = push_stmt_list (); + tree compound_stmt = begin_compound_stmt (0); + current_binding_level->artificial = 1; + + /* FIXME : find some better location to use - perhaps the position of the + function opening brace, if that is available. */ + location_t loc = UNKNOWN_LOCATION; + + /* For other cases, we call a function to process the check. */ + + /* IF we hsve a pre - but not a post, then just emit that. */ + if (!do_post) { - releasing_vec args = build_arg_list (decl1); - tree call = build_call_a (DECL_PRE_FN (decl1), - args->length (), - args->address ()); - CALL_FROM_THUNK_P (call) = true; - finish_expr_stmt (call); + apply_preconditions (fndecl); + add_stmt (fnbody); + finish_compound_stmt (compound_stmt); + return; } + + tree try_fin = build_stmt (loc, TRY_FINALLY_EXPR, NULL_TREE, NULL_TREE); + add_stmt (try_fin); + TREE_OPERAND (try_fin, 0) = push_stmt_list (); + if (do_pre) + /* Add a precondition call, if we have one. */ + apply_preconditions (fndecl); + add_stmt (fnbody); + TREE_OPERAND (try_fin, 0) = pop_stmt_list (TREE_OPERAND (try_fin, 0)); + TREE_OPERAND (try_fin, 1) = push_stmt_list (); + if (!type_noexcept_p (TREE_TYPE (fndecl))) + { + tree eh_else = build_stmt (loc, EH_ELSE_EXPR, NULL_TREE, NULL_TREE); + /* We need to ignore this in constexpr considerations. */ + CONTRACT_EH_ELSE_P (eh_else) = true; + add_stmt (eh_else); + TREE_OPERAND (eh_else, 0) = push_stmt_list (); + apply_postconditions (fndecl); + TREE_OPERAND (eh_else, 0) = pop_stmt_list (TREE_OPERAND (eh_else, 0)); + TREE_OPERAND (eh_else, 1) = push_stmt_list (); + tree rethrow = build_throw (input_location, NULL_TREE, tf_warning_or_error); + suppress_warning (rethrow); + finish_expr_stmt (rethrow); + TREE_OPERAND (eh_else, 1) = pop_stmt_list (TREE_OPERAND (eh_else, 1)); + } + else + apply_postconditions (fndecl); + TREE_OPERAND (try_fin, 1) = pop_stmt_list (TREE_OPERAND (try_fin, 1)); + finish_compound_stmt (compound_stmt); + /* The DECL_SAVED_TREE stmt list will be popped by our caller. */ } + /* Finish up the pre & post function definitions for a guarded FNDECL, and compile those functions all the way to assembler language output. */ @@ -2074,34 +2131,6 @@ finish_function_contracts (tree fndecl) } } -/* Rewrite the expression of a returned expression so that it invokes the - postcondition function as needed. */ - -tree -apply_postcondition_to_return (tree expr) -{ - tree fn = current_function_decl; - tree post = DECL_POST_FN (fn); - if (!post) - return NULL_TREE; - - /* If FN returns in memory, POST has a void return type and we call it when - EXPR is DECL_RESULT (fn). If FN returns a scalar, POST has the same - return type and we call it when EXPR is the value being returned. */ - if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (post))) - != (expr == DECL_RESULT (fn))) - return NULL_TREE; - - releasing_vec args = build_arg_list (fn); - if (get_postcondition_result_parameter (fn)) - vec_safe_push (args, expr); - tree call = build_call_a (post, - args->length (), - args->address ()); - CALL_FROM_THUNK_P (call) = true; - - return call; -} /* A subroutine of duplicate_decls. Diagnose issues in the redeclaration of guarded functions. */ diff --git a/gcc/cp/contracts.h b/gcc/cp/contracts.h index 3e5c30db331..c786affd0e3 100644 --- a/gcc/cp/contracts.h +++ b/gcc/cp/contracts.h @@ -295,8 +295,9 @@ extern void update_late_contract (tree, tree, tree); extern tree splice_out_contracts (tree); extern bool all_attributes_are_contracts_p (tree); extern void inherit_base_contracts (tree, tree); -extern tree apply_postcondition_to_return (tree); +//extern tree apply_postcondition_to_return (tree); extern void start_function_contracts (tree); +extern void maybe_apply_function_contracts (tree); extern void finish_function_contracts (tree); extern void set_contract_functions (tree, tree, tree); extern tree build_contract_check (tree); diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc index 97bc211ff67..f350fc33e9b 100644 --- a/gcc/cp/coroutines.cc +++ b/gcc/cp/coroutines.cc @@ -4014,6 +4014,8 @@ coro_build_actor_or_destroy_function (tree orig, tree fn_type, DECL_USER_ALIGN (fn) = DECL_USER_ALIGN (orig); /* Apply attributes from the original fn. */ DECL_ATTRIBUTES (fn) = copy_list (DECL_ATTRIBUTES (orig)); + /* but we do not want ones for contracts. */ + remove_contract_attributes (fn); /* A void return. */ tree resdecl = build_decl (loc, RESULT_DECL, 0, void_type_node); diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 416c60b7311..505ee5d4dc9 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -451,6 +451,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; ATOMIC_CONSTR_MAP_INSTANTIATED_P (in ATOMIC_CONSTR) contract_semantic (in ASSERTION_, PRECONDITION_, POSTCONDITION_STMT) RETURN_EXPR_LOCAL_ADDR_P (in RETURN_EXPR) + CONTRACT_EH_ELSE_P (in EH_ELSE_EXPR) 1: IDENTIFIER_KIND_BIT_1 (in IDENTIFIER_NODE) TI_PENDING_TEMPLATE_FLAG. TEMPLATE_PARMS_FOR_INLINE. @@ -723,6 +724,8 @@ typedef struct ptrmem_cst * ptrmem_cst_t; #define CLEANUP_P(NODE) TREE_LANG_FLAG_0 (TRY_BLOCK_CHECK (NODE)) +#define CONTRACT_EH_ELSE_P(NODE) TREE_LANG_FLAG_0 (EH_ELSE_EXPR_CHECK (NODE)) + #define BIND_EXPR_TRY_BLOCK(NODE) \ TREE_LANG_FLAG_0 (BIND_EXPR_CHECK (NODE)) diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index 03deb1493a4..f3e733a29ba 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -18599,16 +18599,19 @@ finish_function (bool inline_p) tree fndecl = current_function_decl; tree fntype, ctype = NULL_TREE; tree resumer = NULL_TREE, destroyer = NULL_TREE; - bool coro_p = flag_coroutines - && !processing_template_decl - && DECL_COROUTINE_P (fndecl); - bool coro_emit_helpers = false; /* When we get some parse errors, we can end up without a current_function_decl, so cope. */ - if (fndecl == NULL_TREE) + if (fndecl == NULL_TREE || fndecl == error_mark_node) return error_mark_node; + bool coro_p = flag_coroutines + && !processing_template_decl + && DECL_COROUTINE_P (fndecl); + bool coro_emit_helpers = false; + bool do_contracts = DECL_HAS_CONTRACTS_P (fndecl) + && !processing_template_decl; + if (!DECL_OMP_DECLARE_REDUCTION_P (fndecl)) finish_lambda_scope (); @@ -18644,6 +18647,10 @@ finish_function (bool inline_p) finish_eh_spec_block (TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fndecl)), current_eh_spec_block); + + /* If outlining succeeded, then add contracts handling if needed. */ + if (coro_emit_helpers && do_contracts) + maybe_apply_function_contracts (fndecl); } else /* For a cloned function, we've already got all the code we need; @@ -18659,6 +18666,10 @@ finish_function (bool inline_p) finish_eh_spec_block (TYPE_RAISES_EXCEPTIONS (TREE_TYPE (current_function_decl)), current_eh_spec_block); + + if (do_contracts) + maybe_apply_function_contracts (current_function_decl); + } /* If we're saving up tree structure, tie off the function now. */ @@ -18911,7 +18922,7 @@ finish_function (bool inline_p) --function_depth; /* Clean up. */ - current_function_decl = NULL_TREE; + gcc_checking_assert (current_function_decl == NULL_TREE); invoke_plugin_callbacks (PLUGIN_FINISH_PARSE_FUNCTION, fndecl); diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc index 5970ac3d398..4434ba6e2f4 100644 --- a/gcc/cp/typeck.cc +++ b/gcc/cp/typeck.cc @@ -11416,13 +11416,7 @@ check_return_expr (tree retval, bool *no_warning, bool *dangling) /* Actually copy the value returned into the appropriate location. */ if (retval && retval != result) - { - /* If there's a postcondition for a scalar return value, wrap - retval in a call to the postcondition function. */ - if (tree post = apply_postcondition_to_return (retval)) - retval = post; - retval = cp_build_init_expr (result, retval); - } + retval = cp_build_init_expr (result, retval); if (current_function_return_value == bare_retval) INIT_EXPR_NRV_P (retval) = true; @@ -11430,11 +11424,6 @@ check_return_expr (tree retval, bool *no_warning, bool *dangling) if (tree set = maybe_set_retval_sentinel ()) retval = build2 (COMPOUND_EXPR, void_type_node, retval, set); - /* If there's a postcondition for an aggregate return value, call the - postcondition function after the return object is initialized. */ - if (tree post = apply_postcondition_to_return (result)) - retval = build2 (COMPOUND_EXPR, void_type_node, retval, post); - return retval; } diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc index 622c51d5c3f..43bd4c56d7f 100644 --- a/gcc/gimplify.cc +++ b/gcc/gimplify.cc @@ -227,6 +227,7 @@ struct gimplify_ctx unsigned keep_stack : 1; unsigned save_stack : 1; unsigned in_switch_expr : 1; + unsigned in_handler_expr : 1; }; enum gimplify_defaultmap_kind @@ -18401,10 +18402,12 @@ gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p, input_location = UNKNOWN_LOCATION; eval = cleanup = NULL; gimplify_and_add (TREE_OPERAND (*expr_p, 0), &eval); + bool save_in_handler_expr = gimplify_ctxp->in_handler_expr; if (TREE_CODE (*expr_p) == TRY_FINALLY_EXPR && TREE_CODE (TREE_OPERAND (*expr_p, 1)) == EH_ELSE_EXPR) { gimple_seq n = NULL, e = NULL; + gimplify_ctxp->in_handler_expr = true; gimplify_and_add (TREE_OPERAND (TREE_OPERAND (*expr_p, 1), 0), &n); gimplify_and_add (TREE_OPERAND (TREE_OPERAND (*expr_p, 1), @@ -18416,7 +18419,11 @@ gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p, } } else - gimplify_and_add (TREE_OPERAND (*expr_p, 1), &cleanup); + { + gimplify_ctxp->in_handler_expr = true; + gimplify_and_add (TREE_OPERAND (*expr_p, 1), &cleanup); + } + gimplify_ctxp->in_handler_expr = save_in_handler_expr; /* Don't create bogus GIMPLE_TRY with empty cleanup. */ if (gimple_seq_empty_p (cleanup)) { @@ -18516,6 +18523,10 @@ gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p, /* When within an OMP context, notice uses of variables. */ if (gimplify_omp_ctxp) omp_notice_variable (gimplify_omp_ctxp, *expr_p, true); + /* Handlers can refer to the function result; if that has been + moved, we need to track it. */ + if (gimplify_ctxp->in_handler_expr && gimplify_ctxp->return_temp) + *expr_p = gimplify_ctxp->return_temp; ret = GS_ALL_DONE; break; diff --git a/gcc/testsuite/g++.dg/contracts/pr115434.C b/gcc/testsuite/g++.dg/contracts/pr115434.C new file mode 100644 index 00000000000..e9c847f8969 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/pr115434.C @@ -0,0 +1,16 @@ +// We were failing to apply post conditions to void functions. + +// { dg-do run } +// { dg-options "-std=c++20 -fcontracts -fcontract-continuation-mode=on" } + + +void foo (const int b) +[[ post: b == 9 ]] // contract not checked +{} + +int main() +{ + foo(3); +} + +// { dg-output "contract violation in function foo at .*.C:8: b == 9.*(\n|\r\n|\r)" } diff --git a/gcc/testsuite/g++.dg/coroutines/pr110871.C b/gcc/testsuite/g++.dg/coroutines/pr110871.C new file mode 100644 index 00000000000..8a667c856ae --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/pr110871.C @@ -0,0 +1,62 @@ +// { dg-additional-options "-fcontracts -fcontract-continuation-mode=on" } +// { dg-do run } +#include +#include + +// In order to test the contract violation diagnostic, we have to set +// -fcontract-continuation-mode=on; this means that the code will emit +// the message below - but before that the contract should have been checked. +void process(int from, int to) +{ + if (from > to) + std::cout << "would have been a disaster!" << std::endl; +} + +template +struct generator +{ + struct promise_type + { + template + promise_type(Args&&... args) { + std::cout << "promise init" << std::endl; + process(args...); + } + + std::suspend_always yield_value(T) { return {}; } + + std::suspend_always initial_suspend() const noexcept { return {}; } + std::suspend_never final_suspend() const noexcept { return {}; } + void unhandled_exception() noexcept {} + + generator get_return_object() noexcept { return {}; } + }; +}; + +namespace std { +template +struct coroutine_traits, Args...> +{ + using promise_type = typename generator::promise_type; +}; + +}; + +generator seq(int from, int to) [[pre: from <= to]] + +{ + std::cout << "coro initial" << std::endl; + for (int i = from; i <= to; ++i) { + co_yield i; + std::cout << "coro resumed" << std::endl; + } +} + +int main() { + std::cout << "main initial" << std::endl; + generator s = seq(10, 5); + (void)s; + std::cout << "main continues" << std::endl; +} + +// { dg-output "contract violation in function seq at .*.C:45: from \<= to.*(\n|\r\n|\r)" } diff --git a/gcc/testsuite/g++.dg/coroutines/pr110872.C b/gcc/testsuite/g++.dg/coroutines/pr110872.C new file mode 100644 index 00000000000..a809986f296 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/pr110872.C @@ -0,0 +1,49 @@ +// { dg-additional-options "-fcontracts -fcontract-continuation-mode=on" } +// { dg-do run } + +#include +#include + + +template +struct generator +{ + struct promise_type + { + std::suspend_always yield_value(T) { return {}; } + + std::suspend_always initial_suspend() const noexcept { return {}; } + std::suspend_never final_suspend() const noexcept { return {}; } + void unhandled_exception() noexcept {} + + generator get_return_object() noexcept { return {}; } + }; + + bool is_valid() { return false; } +}; + +namespace std { +template +struct coroutine_traits, Args...> +{ + using promise_type = typename generator::promise_type; +}; + +}; + +generator val(int v) +[[post g: g.is_valid()]] +{ + std::cout << "coro initial" << std::endl; + co_yield v; + std::cout << "coro resumed" << std::endl; +} + +int main() { + std::cout << "main initial" << std::endl; + generator s = val(1); + (void)s; + std::cout << "main continues" << std::endl; +} + +// { dg-output "contract violation in function val at .*.C:35: g.is_valid().*(\n|\r\n|\r)" }