From patchwork Thu Aug 22 11:29:46 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Iain Sandoe X-Patchwork-Id: 1975454 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20230601 header.b=MuP2SSWp; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=8.43.85.97; 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 [8.43.85.97]) (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 4WqLbZ2RBTz1yYZ for ; Thu, 22 Aug 2024 21:30:18 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 3F9663870C28 for ; Thu, 22 Aug 2024 11:30:16 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from mail-lf1-x130.google.com (mail-lf1-x130.google.com [IPv6:2a00:1450:4864:20::130]) by sourceware.org (Postfix) with ESMTPS id 341CD3858283 for ; Thu, 22 Aug 2024 11:29:50 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 341CD3858283 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 341CD3858283 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2a00:1450:4864:20::130 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1724326194; cv=none; b=Kug7MLAYxMy1/d1VC0MTo9T5cfChu7u2QzDABxft9XncyAOqa/JGxpwPQrl7LoxKXOIhk6zczVBCKhT6VSFCfn9UPyFcl/hazLxINxhqWxXttWck5mIWf3Y14m/l6bQ4kvTV+cG4nljimGFv09+J8NlasU/IqOlfP3dM8uO6vC0= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1724326194; c=relaxed/simple; bh=mSPVAu8zGi42ta+kLIsrCuVoXxWq88IGieRNM/Rgn4w=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=r5inYfGFOl4Tz+ssTQkt2w019kehHhIW5H2iZAWYVSLjctRwn0Pjfkavn9SMjrlXCX9jVaCiKpOwo5ACWx6u/FrqsZYcaGayrbcqCvz1+66wmhiCLtx9rwoUsjP3VfGT1lKda7SPG5RMjUx/6eYqjqtT7XJl5aBwnM/Xb1PPyRw= ARC-Authentication-Results: i=1; server2.sourceware.org Received: by mail-lf1-x130.google.com with SMTP id 2adb3069b0e04-5334879ba28so863278e87.3 for ; Thu, 22 Aug 2024 04:29:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724326189; x=1724930989; darn=gcc.gnu.org; h=content-transfer-encoding:mime-version:reply-to:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to; bh=dH0XcHhJjFpFxsYtCCAasQzdjfMyQO3oNMmi7yhyLhY=; b=MuP2SSWpB0IqQ2QB9Ni5yVGFMl5yUxp7QOVeNiHS8utp/kBaIMYcL5Hk0Tce+pjDAl 8Mx5iffG5Xq4wc1mXh19cUs8OZRrRogtSceFHYxceBax1QS07RJZyKvwYRmgHHqiK/Yw X20j50eTsZqJ7Yeikkj3fPFCwRBYlv0VOQY93GW7o7dzps2A8a1oKvFFIB9rXc/nw6uE 3MHitHh1faXmMfkkJ20yaFCTM5SBM9sIMCLnUUsF2SfgvL6hSFPux9Sz7ir8ULoFcwIS uu08HZtKVtRJap1etJH8GIc3ImjhAxOJyZLlj3EjzAFJZvgPBfe8MGK3OAQSw9ihaoKp PJYQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724326189; x=1724930989; h=content-transfer-encoding:mime-version:reply-to:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=dH0XcHhJjFpFxsYtCCAasQzdjfMyQO3oNMmi7yhyLhY=; b=VZvFiTBWsad4VJtmobhrW29UhQzR+dGF7HPIj4WkKGHl24udFOfGOdMnvb/nvhAI5Y yptrxz0wSqe3APzwYSQ0KVUbF2UcKGQ4GwbZBld+g4YFsnv48t1cuwKbFC6xr4eGTmIp sJ6QmBSfF1vOtKPmWpDB2CTSJHtYPumjfdz+I2ZmlNb5DUOc4rInpHQUE/Kk6JZe80Kd 449YF+Hjd6t6ueV9uCNfrdFwUjrzxXnncd1/owRId2Oq4bRRys0iSgmi6s487mdVcc7d X7gIsd4NO0Q7+Hsul5u7QARGfgfM4x8Iuo5xAAZV+P5M7R+lzNn3SgJOd912DhETAGbF kqsQ== X-Gm-Message-State: AOJu0Yy7UC+TA2hCxSbbbBhl6oozG6Rs/WIaxjFzU6P0uJQVcBXH4jtL 3v9J7jwB7VwJbsIPGxkoQ6+G50frinIgVvZ7tej4w50QpDR6y7tcBAUyFFI+ X-Google-Smtp-Source: AGHT+IFgXpvJSDUH9QSGHupA3il2yiChekP2oVD1Dbx1oNiERe99m6GqeVBFBd3+PUnF6wkUoBtR1Q== X-Received: by 2002:a05:6512:239e:b0:52c:b479:902d with SMTP id 2adb3069b0e04-5334855018emr4291505e87.4.1724326187611; Thu, 22 Aug 2024 04:29:47 -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 a640c23a62f3a-a868f22039csm106691366b.25.2024.08.22.04.29.46 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Thu, 22 Aug 2024 04:29:47 -0700 (PDT) From: Iain Sandoe X-Google-Original-From: Iain Sandoe To: gcc-patches@gcc.gnu.org Cc: jason@redhat.com Subject: [PATCH v2] c++, coroutines: Split the ramp build into a separate function. Date: Thu, 22 Aug 2024 12:29:46 +0100 Message-Id: <20240822112946.30485-1-iain@sandoe.co.uk> X-Mailer: git-send-email 2.39.2 (Apple Git-143) In-Reply-To: References: 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 >>+ tree fn_return_type = TREE_TYPE (TREE_TYPE (orig)); >> /* Ramp: */ >>+ tree stmt = begin_function_body (); >The name "stmt" doesn't suggest to me that it's holding the result of begin_function_body. Maybe "ramp_fnbody"? Of course, then there's some confusion with "ramp_body". Should that also change? I've changed it as suggested and made the other variable 'ramp_outer_bind' we are not done with changes in this code; there is more simplification to come. >>+ Do this in reverse order from their creation. */ >>+ vec worklist = vNULL; >I think this should be auto_vec, to avoid leaking the vec when the function returns. done. >>+ /* FIXME: This has the hidden side-effect of preparing the current function >>+ to be the ramp. */ >This comment seems obsolete? updated the comment to match current status. >>+ /* If the original function has a return value with a non-trivial DTOR >>+ and the body contains a var with a DTOR that might throw, the decl is >>+ marked "throwing_cleanup". >>+ We do not [in the ramp, which is synthesised here], use any body var >>+ types with DTORs that might throw. >>+ The original body is transformed into the actor function which only >>+ contains void returns, and is also wrapped in a try-catch block. >>+ So (a) the 'throwing_cleanup' is not correct for the ramp and (b) we do >>+ not need to transfer it to the actor which only contains void returns. */ >>+ cp_function_chain->throwing_cleanup = false; >Do we want to clear other members of cp_function_chain as well? return_value, returns_value, returns_null, returns_abnormally, infinite_loop, can_throw, invalid_constexpr, x_named_labels, bindings, infinite_loops all seem disconnected from the spliced definition. This code does not (quite) DTRT and is going to be moved in patch #8; I think we should defer any such changes until that patch, rather than doing here and then moving them? >>+ hash_map *param_uses = analyze_fn_parms (orig); >This map seems to leak. How about making it a local variable (like suspend_points) and passing its address to analyze_fn_parms? fixed. >>+ /* 4. Now make space for local vars, this is conservative again, and we >>+ would expect to delete unused entries later. Compose the frame entries >>+ list. */ >Is this comment still accurate? It seems surprising given that you're collecting uses. Revised the comment to match the current impl. >>+ vec param_dtor_list = vNULL; >This should also be auto_vec. done. Retested on x86_64-darwin, OK for trunk? thanks Iain --- 8< --- This is primarily preparation to partition the functionality of the coroutine transform into analysis, ramp generation and then (later) synthesis of the coroutine body. The patch does fix one latent issue in the ordering of DTORs for frame parameter copies (to ensure that they are processed in reverse order to the copy creation). gcc/cp/ChangeLog: * coroutines.cc (build_actor_fn): Arrange to apply any required parameter copy DTORs in reverse order to their creation. (coro_rewrite_function_body): Handle revised param uses. (morph_fn_to_coro): Split the ramp function completion into a separate function. (build_ramp_function): New. Signed-off-by: Iain Sandoe --- gcc/cp/coroutines.cc | 384 ++++++++++++++++++++++--------------------- 1 file changed, 201 insertions(+), 183 deletions(-) diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc index 12625f51bfb..5a5513ef769 100644 --- a/gcc/cp/coroutines.cc +++ b/gcc/cp/coroutines.cc @@ -2298,7 +2298,7 @@ static void build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody, tree orig, hash_map *local_var_uses, hash_map *suspend_points, - vec *param_dtor_list, + vec *param_dtor_list, tree resume_idx_var, unsigned body_count, tree frame_size) { verify_stmt_tree (fnbody); @@ -2513,19 +2513,15 @@ build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody, fnf2_x = build1 (CONVERT_EXPR, integer_type_node, fnf2_x); tree cmp = build2 (NE_EXPR, integer_type_node, fnf2_x, integer_zero_node); finish_if_stmt_cond (cmp, need_free_if); - if (param_dtor_list != NULL) + while (!param_dtor_list->is_empty ()) { - int i; - tree pid; - FOR_EACH_VEC_ELT (*param_dtor_list, i, pid) - { - tree m - = lookup_member (coro_frame_type, pid, 1, 0, tf_warning_or_error); - tree a = build_class_member_access_expr (actor_frame, m, NULL_TREE, - false, tf_warning_or_error); - if (tree dtor = cxx_maybe_build_cleanup (a, tf_warning_or_error)) - add_stmt (dtor); - } + tree pid = param_dtor_list->pop (); + tree m = lookup_member (coro_frame_type, pid, 1, 0, tf_warning_or_error); + gcc_checking_assert (m); + tree a = build_class_member_access_expr (actor_frame, m, NULL_TREE, + false, tf_warning_or_error); + if (tree dtor = cxx_maybe_build_cleanup (a, tf_warning_or_error)) + add_stmt (dtor); } /* Build the frame DTOR. */ @@ -3965,13 +3961,11 @@ rewrite_param_uses (tree *stmt, int *do_subtree ATTRIBUTE_UNUSED, void *d) /* Build up a set of info that determines how each param copy will be handled. */ -static hash_map * -analyze_fn_parms (tree orig) +static void +analyze_fn_parms (tree orig, hash_map *param_uses) { if (!DECL_ARGUMENTS (orig)) - return NULL; - - hash_map *param_uses = new hash_map; + return; /* Build a hash map with an entry for each param. The key is the param tree. @@ -4038,8 +4032,6 @@ analyze_fn_parms (tree orig) else parm.trivial_dtor = true; } - - return param_uses; } /* Small helper for the repetitive task of adding a new field to the coro @@ -4339,13 +4331,13 @@ coro_rewrite_function_body (location_t fn_start, tree fnbody, tree orig, add_decl_expr (var); /* If we have function parms, then these will be copied to the coroutine - frame. Create a local (proxy) variable for each parm, since the original - parms will be out of scope once the ramp has finished. The proxy vars will + frame as per [dcl.fct.def.coroutine] / 13. + Here, we create a local (proxy) variable for each parm, since the original + parms will be out of scope once the ramp has finished. The proxy vars will get DECL_VALUE_EXPRs pointing to the frame copies, so that we can interact with them in the debugger. */ - if (param_uses) + if (DECL_ARGUMENTS (orig)) { - gcc_checking_assert (DECL_ARGUMENTS (orig)); /* Add a local var for each parm. */ for (tree arg = DECL_ARGUMENTS (orig); arg != NULL; arg = DECL_CHAIN (arg)) @@ -4360,7 +4352,7 @@ coro_rewrite_function_body (location_t fn_start, tree fnbody, tree orig, DECL_CHAIN (parm_i->copy_var) = var_list; var_list = parm_i->copy_var; add_decl_expr (parm_i->copy_var); - } + } /* Now replace all uses of the parms in the function body with the proxy vars. We want to this to apply to every instance of param's use, so @@ -4553,151 +4545,32 @@ split_coroutine_body_from_ramp (tree fndecl) return body; } -/* Here we: - a) Check that the function and promise type are valid for a - coroutine. - b) Carry out the initial morph to create the skeleton of the - coroutine ramp function and the rewritten body. +/* Build the ramp function. + Here we take the original function definition which has now had its body + removed, and use it as the declaration of the ramp which both replaces the + user's written function at call sites, and is responsible for starting + the coroutine it defined. + returns NULL_TREE on error or an expression for the frame size. - Assumptions. + We should arrive here with the state of the compiler as if we had just + executed start_preparsed_function(). */ - 1. We only hit this code once all dependencies are resolved. - 2. The function body will be either a bind expr or a statement list - 3. That cfun and current_function_decl are valid for the case we're - expanding. - 4. 'input_location' will be of the final brace for the function. - - We do something like this: - declare a dummy coro frame. - struct _R_frame { - using handle_type = coro::coroutine_handle; - void (*_Coro_resume_fn)(_R_frame *); - void (*_Coro_destroy_fn)(_R_frame *); - coro1::promise_type _Coro_promise; - bool _Coro_frame_needs_free; free the coro frame mem if set. - bool _Coro_i_a_r_c; [dcl.fct.def.coroutine] / 5.3 - short _Coro_resume_index; - handle_type _Coro_self_handle; - parameter copies (were required). - local variables saved (including awaitables) - (maybe) trailing space. - }; */ - -bool -morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer) +static tree +build_ramp_function (tree orig, location_t fn_start, tree coro_frame_ptr, + tree coro_frame_type, + hash_map *param_uses, + tree act_des_fn_ptr, tree actor, tree destroy, + vec *param_dtor_list) { - gcc_checking_assert (orig && TREE_CODE (orig) == FUNCTION_DECL); - - *resumer = error_mark_node; - *destroyer = error_mark_node; - if (!coro_function_valid_p (orig)) - { - /* For early errors, we do not want a diagnostic about the missing - ramp return value, since the user cannot fix this - a 'return' is - not allowed in a coroutine. */ - suppress_warning (orig, OPT_Wreturn_type); - /* Discard the body, we can't process it further. */ - pop_stmt_list (DECL_SAVED_TREE (orig)); - DECL_SAVED_TREE (orig) = push_stmt_list (); - /* Match the expected nesting when an eh block is in use. */ - if (use_eh_spec_block (orig)) - current_eh_spec_block = begin_eh_spec_block (); - return false; - } - - /* We don't have the locus of the opening brace - it's filled in later (and - there doesn't really seem to be any easy way to get at it). - The closing brace is assumed to be input_location. */ - location_t fn_start = DECL_SOURCE_LOCATION (orig); - - /* FIXME: This has the hidden side-effect of preparing the current function - to be the ramp. */ - tree fnbody = split_coroutine_body_from_ramp (orig); - if (!fnbody) - return false; - - /* If the original function has a return value with a non-trivial DTOR - and the body contains a var with a DTOR that might throw, the decl is - marked "throwing_cleanup". - We do not [in the ramp, which is synthesised here], use any body var - types with DTORs that might throw. - The original body is transformed into the actor function which only - contains void returns, and is also wrapped in a try-catch block. - So (a) the 'throwing_cleanup' is not correct for the ramp and (b) we do - not need to transfer it to the actor which only contains void returns. */ - cp_function_chain->throwing_cleanup = false; - - /* Create the coro frame type, as far as it can be known at this stage. - 1. Types we already know. */ - - tree fn_return_type = TREE_TYPE (TREE_TYPE (orig)); tree promise_type = get_coroutine_promise_type (orig); - - /* 2. Types we need to define or look up. */ - - tree fr_name = get_fn_local_identifier (orig, "Frame"); - tree coro_frame_type = xref_tag (record_type, fr_name); - DECL_CONTEXT (TYPE_NAME (coro_frame_type)) = DECL_CONTEXT (orig); - tree coro_frame_ptr = build_pointer_type (coro_frame_type); - tree act_des_fn_type - = build_function_type_list (void_type_node, coro_frame_ptr, NULL_TREE); - tree act_des_fn_ptr = build_pointer_type (act_des_fn_type); - - /* Declare the actor and destroyer function. */ - tree actor = coro_build_actor_or_destroy_function (orig, act_des_fn_type, - coro_frame_ptr, true); - tree destroy = coro_build_actor_or_destroy_function (orig, act_des_fn_type, - coro_frame_ptr, false); - - /* Construct the wrapped function body; we will analyze this to determine - the requirements for the coroutine frame. */ - - tree resume_idx_var = NULL_TREE; - tree fs_label = NULL_TREE; - hash_map *param_uses = analyze_fn_parms (orig); - - fnbody = coro_rewrite_function_body (fn_start, fnbody, orig, param_uses, - act_des_fn_ptr, - resume_idx_var, fs_label); - /* Build our dummy coro frame layout. */ - coro_frame_type = begin_class_definition (coro_frame_type); - - - /* We need to know, and inspect, each suspend point in the function - in several places. It's convenient to place this map out of line - since it's used from tree walk callbacks. */ - - hash_map suspend_points; - /* Now insert the data for any body await points, at this time we also need - to promote any temporaries that are captured by reference (to regular - vars) they will get added to the coro frame along with other locals. */ - susp_frame_data body_aw_points (fs_label, &suspend_points); - cp_walk_tree (&fnbody, await_statement_walker, &body_aw_points, NULL); - - /* 4. Now make space for local vars, this is conservative again, and we - would expect to delete unused entries later. Compose the frame entries - list. */ - - /* The fields for the coro frame. */ - tree field_list = NULL_TREE; - hash_map local_var_uses; - local_vars_frame_data local_vars_data (&field_list, &local_var_uses); - cp_walk_tree (&fnbody, register_local_var_uses, &local_vars_data, NULL); - - /* Tie off the struct for now, so that we can build offsets to the - known entries. */ - TYPE_FIELDS (coro_frame_type) = field_list; - TYPE_BINFO (coro_frame_type) = make_tree_binfo (0); - BINFO_OFFSET (TYPE_BINFO (coro_frame_type)) = size_zero_node; - BINFO_TYPE (TYPE_BINFO (coro_frame_type)) = coro_frame_type; - - coro_frame_type = finish_struct (coro_frame_type, NULL_TREE); + tree fn_return_type = TREE_TYPE (TREE_TYPE (orig)); /* Ramp: */ + tree ramp_fnbody = begin_function_body (); /* Now build the ramp function pieces. */ tree ramp_bind = build3 (BIND_EXPR, void_type_node, NULL, NULL, NULL); add_stmt (ramp_bind); - tree ramp_body = push_stmt_list (); + tree ramp_outer_bind = push_stmt_list (); tree zeroinit = build1_loc (fn_start, CONVERT_EXPR, coro_frame_ptr, nullptr_node); @@ -5018,8 +4891,6 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer) parameter copies ends immediately after the lifetime of the coroutine promise object ends. */ - vec *param_dtor_list = NULL; - if (DECL_ARGUMENTS (orig)) { promise_args = make_tree_vector (); @@ -5064,9 +4935,7 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer) finish_expr_stmt (r); if (!parm.trivial_dtor) { - if (param_dtor_list == NULL) - param_dtor_list = make_tree_vector (); - vec_safe_push (param_dtor_list, parm.field_id); + param_dtor_list->safe_push (parm.field_id); /* Cleanup this frame copy on exception. */ parm.fr_copy_dtor = cxx_maybe_build_cleanup (fld_idx, tf_warning_or_error); @@ -5143,10 +5012,10 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer) /* Without a return object we haven't got much clue what's going on. */ if (get_ro == error_mark_node) { - BIND_EXPR_BODY (ramp_bind) = pop_stmt_list (ramp_body); + BIND_EXPR_BODY (ramp_bind) = pop_stmt_list (ramp_outer_bind); /* Suppress warnings about the missing return value. */ suppress_warning (orig, OPT_Wreturn_type); - return false; + return NULL_TREE; } tree gro_context_body = push_stmt_list (); @@ -5318,7 +5187,9 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer) add_stmt (promise_d_if); } - /* Clean up any frame copies of parms with non-trivial dtors. */ + /* Clean up any frame copies of parms with non-trivial dtors. + Do this in reverse order from their creation. */ + auto_vec worklist; if (DECL_ARGUMENTS (orig)) for (tree arg = DECL_ARGUMENTS (orig); arg != NULL; arg = DECL_CHAIN (arg)) @@ -5326,18 +5197,23 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer) param_info *parm_i = param_uses->get (arg); if (parm_i->trivial_dtor) continue; - if (parm_i->fr_copy_dtor && parm_i->fr_copy_dtor != error_mark_node) - { - tree dtor_if = begin_if_stmt (); - finish_if_stmt_cond (parm_i->guard_var, dtor_if); - finish_expr_stmt (parm_i->fr_copy_dtor); - finish_then_clause (dtor_if); - tree parm_d_if_scope = IF_SCOPE (dtor_if); - IF_SCOPE (dtor_if) = NULL; - dtor_if = do_poplevel (parm_d_if_scope); - add_stmt (dtor_if); - } + worklist.safe_push (parm_i); } + while (!worklist.is_empty()) + { + param_info *parm_i = worklist.pop (); + if (parm_i->fr_copy_dtor && parm_i->fr_copy_dtor != error_mark_node) + { + tree dtor_if = begin_if_stmt (); + finish_if_stmt_cond (parm_i->guard_var, dtor_if); + finish_expr_stmt (parm_i->fr_copy_dtor); + finish_then_clause (dtor_if); + tree parm_d_if_scope = IF_SCOPE (dtor_if); + IF_SCOPE (dtor_if) = NULL; + dtor_if = do_poplevel (parm_d_if_scope); + add_stmt (dtor_if); + } + } /* We always expect to delete the frame. */ tree del_coro_fr = coro_get_frame_dtor (coro_fp, orig, frame_size, @@ -5350,8 +5226,150 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer) TRY_HANDLERS (ramp_cleanup) = pop_stmt_list (TRY_HANDLERS (ramp_cleanup)); } - BIND_EXPR_BODY (ramp_bind) = pop_stmt_list (ramp_body); - TREE_SIDE_EFFECTS (ramp_bind) = true; + BIND_EXPR_BODY (ramp_bind) = pop_stmt_list (ramp_outer_bind); + finish_function_body (ramp_fnbody); + return frame_size; +} + +/* Here we: + a) Check that the function and promise type are valid for a + coroutine. + b) Carry out the initial morph to create the skeleton of the + coroutine ramp function and the rewritten body. + + Assumptions. + + 1. We only hit this code once all dependencies are resolved. + 2. The function body will be either a bind expr or a statement list + 3. That cfun and current_function_decl are valid for the case we're + expanding. + 4. 'input_location' will be of the final brace for the function. + + We do something like this: + declare a dummy coro frame. + struct _R_frame { + using handle_type = coro::coroutine_handle; + void (*_Coro_resume_fn)(_R_frame *); + void (*_Coro_destroy_fn)(_R_frame *); + coro1::promise_type _Coro_promise; + bool _Coro_frame_needs_free; free the coro frame mem if set. + bool _Coro_i_a_r_c; [dcl.fct.def.coroutine] / 5.3 + short _Coro_resume_index; + handle_type _Coro_self_handle; + parameter copies (were required). + local variables saved (including awaitables) + (maybe) trailing space. + }; */ + +bool +morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer) +{ + gcc_checking_assert (orig && TREE_CODE (orig) == FUNCTION_DECL); + + *resumer = error_mark_node; + *destroyer = error_mark_node; + if (!coro_function_valid_p (orig)) + { + /* For early errors, we do not want a diagnostic about the missing + ramp return value, since the user cannot fix this - a 'return' is + not allowed in a coroutine. */ + suppress_warning (orig, OPT_Wreturn_type); + /* Discard the body, we can't process it further. */ + pop_stmt_list (DECL_SAVED_TREE (orig)); + DECL_SAVED_TREE (orig) = push_stmt_list (); + /* Match the expected nesting when an eh block is in use. */ + if (use_eh_spec_block (orig)) + current_eh_spec_block = begin_eh_spec_block (); + return false; + } + + /* We don't have the locus of the opening brace - it's filled in later (and + there doesn't really seem to be any easy way to get at it). + The closing brace is assumed to be input_location. */ + location_t fn_start = DECL_SOURCE_LOCATION (orig); + + /* Extract the function body that we will outline (as the actor) leaving + current_function_decl in a state ready to build the ramp body. */ + tree fnbody = split_coroutine_body_from_ramp (orig); + if (!fnbody) + return false; + + /* If the original function has a return value with a non-trivial DTOR + and the body contains a var with a DTOR that might throw, the decl is + marked "throwing_cleanup". + We do not [in the ramp, which is synthesised here], use any body var + types with DTORs that might throw. + The original body is transformed into the actor function which only + contains void returns, and is also wrapped in a try-catch block. + So (a) the 'throwing_cleanup' is not correct for the ramp and (b) we do + not need to transfer it to the actor which only contains void returns. */ + cp_function_chain->throwing_cleanup = false; + + /* Create the coro frame type, as far as it can be known at this stage. + 1. Types we already know. */ + + /* 2. Types we need to define or look up. */ + + tree fr_name = get_fn_local_identifier (orig, "Frame"); + tree coro_frame_type = xref_tag (record_type, fr_name); + DECL_CONTEXT (TYPE_NAME (coro_frame_type)) = DECL_CONTEXT (orig); //current_scope (); + tree coro_frame_ptr = build_pointer_type (coro_frame_type); + tree act_des_fn_type + = build_function_type_list (void_type_node, coro_frame_ptr, NULL_TREE); + tree act_des_fn_ptr = build_pointer_type (act_des_fn_type); + + /* Construct the wrapped function body; we will analyze this to determine + the requirements for the coroutine frame. */ + + tree resume_idx_var = NULL_TREE; + tree fs_label = NULL_TREE; + hash_map param_uses; + analyze_fn_parms (orig, ¶m_uses); + + fnbody = coro_rewrite_function_body (fn_start, fnbody, orig, ¶m_uses, + act_des_fn_ptr, + resume_idx_var, fs_label); + + /* We need to know, and inspect, each suspend point in the function + in several places. It's convenient to place this map out of line + since it's used from tree walk callbacks. */ + + hash_map suspend_points; + /* Now insert the data for any body await points, at this time we also need + to promote any temporaries that are captured by reference (to regular + vars) they will get added to the coro frame along with other locals. */ + susp_frame_data body_aw_points (fs_label, &suspend_points); + cp_walk_tree (&fnbody, await_statement_walker, &body_aw_points, NULL); + + /* 4. Now make space for local vars, and compose the frame entries list. */ + + tree field_list = NULL_TREE; + hash_map local_var_uses; + local_vars_frame_data local_vars_data (&field_list, &local_var_uses); + cp_walk_tree (&fnbody, register_local_var_uses, &local_vars_data, NULL); + + /* Tie off the struct for now, so that we can build offsets to the + known entries. */ + coro_frame_type = begin_class_definition (coro_frame_type); + TYPE_FIELDS (coro_frame_type) = field_list; + TYPE_BINFO (coro_frame_type) = make_tree_binfo (0); + BINFO_OFFSET (TYPE_BINFO (coro_frame_type)) = size_zero_node; + BINFO_TYPE (TYPE_BINFO (coro_frame_type)) = coro_frame_type; + coro_frame_type = finish_struct (coro_frame_type, NULL_TREE); + + /* Declare the actor and destroyer function. */ + tree actor = coro_build_actor_or_destroy_function (orig, act_des_fn_type, + coro_frame_ptr, true); + tree destroy = coro_build_actor_or_destroy_function (orig, act_des_fn_type, + coro_frame_ptr, false); + + auto_vec param_dtor_list; + tree frame_size + = build_ramp_function (orig, fn_start, coro_frame_ptr, coro_frame_type, + ¶m_uses, act_des_fn_ptr, actor, destroy, + ¶m_dtor_list); + if (!frame_size) + return false; /* Start to build the final functions. @@ -5362,7 +5380,7 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer) /* Build the actor... */ build_actor_fn (fn_start, coro_frame_type, actor, fnbody, orig, - &local_var_uses, &suspend_points, param_dtor_list, + &local_var_uses, &suspend_points, ¶m_dtor_list, resume_idx_var, body_aw_points.await_number, frame_size); /* Destroyer ... */