diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 8c498fabbcd..064bb38a674 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,29 @@ +2020-01-18 Iain Sandoe + + * Makefile.in: Add coroutine-passes.o. + * builtin-types.def (BT_CONST_SIZE): New. + (BT_FN_BOOL_PTR): New. + (BT_FN_PTR_PTR_CONST_SIZE_BOOL): New. + * builtins.def (DEF_COROUTINE_BUILTIN): New. + * coroutine-builtins.def: New file. + * coroutine-passes.cc: New file. + * function.h (struct GTY function): Add a bit to indicate that the + function is a coroutine component. + * internal-fn.c (expand_CO_FRAME): New. + (expand_CO_YIELD): New. + (expand_CO_SUSPN): New. + (expand_CO_ACTOR): New. + * internal-fn.def (CO_ACTOR): New. + (CO_YIELD): New. + (CO_SUSPN): New. + (CO_FRAME): New. + * passes.def: Add pass_coroutine_lower_builtins, + pass_coroutine_early_expand_ifns. + * tree-pass.h (make_pass_coroutine_lower_builtins): New. + (make_pass_coroutine_early_expand_ifns): New. + * doc/invoke.texi: Document the fcoroutines command line + switch. + 2020-01-18 Jakub Jelinek * config/arm/vfp.md (*clear_vfp_multiple): Remove unused variable. diff --git a/gcc/Makefile.in b/gcc/Makefile.in index c86fc7f41dc..b1423d1dbfd 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1292,6 +1292,7 @@ OBJS = \ compare-elim.o \ context.o \ convert.o \ + coroutine-passes.o \ coverage.o \ cppbuiltin.o \ cppdefault.o \ diff --git a/gcc/builtin-types.def b/gcc/builtin-types.def index b2a5a8be6cb..c7aa691b243 100644 --- a/gcc/builtin-types.def +++ b/gcc/builtin-types.def @@ -131,6 +131,8 @@ DEF_PRIMITIVE_TYPE (BT_CONST_DOUBLE_PTR, DEF_PRIMITIVE_TYPE (BT_LONGDOUBLE_PTR, long_double_ptr_type_node) DEF_PRIMITIVE_TYPE (BT_PID, pid_type_node) DEF_PRIMITIVE_TYPE (BT_SIZE, size_type_node) +DEF_PRIMITIVE_TYPE (BT_CONST_SIZE, + build_qualified_type (size_type_node, TYPE_QUAL_CONST)) DEF_PRIMITIVE_TYPE (BT_SSIZE, signed_size_type_node) DEF_PRIMITIVE_TYPE (BT_WINT, wint_type_node) DEF_PRIMITIVE_TYPE (BT_STRING, string_type_node) @@ -300,6 +302,7 @@ DEF_FUNCTION_TYPE_1 (BT_FN_UINT32_UINT32, BT_UINT32, BT_UINT32) DEF_FUNCTION_TYPE_1 (BT_FN_UINT64_UINT64, BT_UINT64, BT_UINT64) DEF_FUNCTION_TYPE_1 (BT_FN_UINT64_FLOAT, BT_UINT64, BT_FLOAT) DEF_FUNCTION_TYPE_1 (BT_FN_BOOL_INT, BT_BOOL, BT_INT) +DEF_FUNCTION_TYPE_1 (BT_FN_BOOL_PTR, BT_BOOL, BT_PTR) DEF_FUNCTION_TYPE_1 (BT_FN_PTR_CONST_PTR, BT_PTR, BT_CONST_PTR) DEF_FUNCTION_TYPE_1 (BT_FN_CONST_PTR_CONST_PTR, BT_CONST_PTR, BT_CONST_PTR) DEF_FUNCTION_TYPE_1 (BT_FN_UINT16_UINT32, BT_UINT16, BT_UINT32) @@ -628,6 +631,8 @@ DEF_FUNCTION_TYPE_3 (BT_FN_VOID_UINT32_UINT32_PTR, DEF_FUNCTION_TYPE_3 (BT_FN_VOID_SIZE_SIZE_PTR, BT_VOID, BT_SIZE, BT_SIZE, BT_PTR) DEF_FUNCTION_TYPE_3 (BT_FN_UINT_UINT_PTR_PTR, BT_UINT, BT_UINT, BT_PTR, BT_PTR) +DEF_FUNCTION_TYPE_3 (BT_FN_PTR_PTR_CONST_SIZE_BOOL, + BT_PTR, BT_PTR, BT_CONST_SIZE, BT_BOOL) DEF_FUNCTION_TYPE_4 (BT_FN_SIZE_CONST_PTR_SIZE_SIZE_FILEPTR, BT_SIZE, BT_CONST_PTR, BT_SIZE, BT_SIZE, BT_FILEPTR) diff --git a/gcc/builtins.def b/gcc/builtins.def index 674ca239ecf..5ab842c34c2 100644 --- a/gcc/builtins.def +++ b/gcc/builtins.def @@ -189,6 +189,12 @@ along with GCC; see the file COPYING3. If not see DEF_BUILTIN (ENUM, NAME, BUILT_IN_NORMAL, BT_LAST, BT_LAST, false, false, \ false, ATTR_LAST, false, false) +/* Builtins used in implementing coroutine support. */ +#undef DEF_COROUTINE_BUILTIN +#define DEF_COROUTINE_BUILTIN(ENUM, NAME, TYPE, ATTRS) \ + DEF_BUILTIN (ENUM, "__builtin_coro_" NAME, BUILT_IN_NORMAL, TYPE, TYPE, \ + true, true, true, ATTRS, true, flag_coroutines) + /* Builtin used by the implementation of OpenACC and OpenMP. Few of these are actually implemented in the compiler; most are in libgomp. */ /* These builtins also need to be enabled in offloading compilers invoked from @@ -1064,6 +1070,9 @@ DEF_GCC_BUILTIN (BUILT_IN_LINE, "LINE", BT_FN_INT, ATTR_NOTHROW_LEAF_LIST) /* Sanitizer builtins. */ #include "sanitizer.def" +/* Coroutine builtins. */ +#include "coroutine-builtins.def" + /* Do not expose the BRIG builtins by default gcc-wide, but only privately in the BRIG FE as long as there are no references for them in the middle end or any of the upstream backends. */ diff --git a/gcc/c-family/ChangeLog b/gcc/c-family/ChangeLog index f21142aa19e..09ba2c8b40f 100644 --- a/gcc/c-family/ChangeLog +++ b/gcc/c-family/ChangeLog @@ -1,3 +1,13 @@ +2020-01-18 Iain Sandoe + + * c-common.c (co_await, co_yield, co_return): New. + * c-common.h (RID_CO_AWAIT, RID_CO_YIELD, + RID_CO_RETURN): New enumeration values. + (D_CXX_COROUTINES): Bit to identify coroutines are active. + (D_CXX_COROUTINES_FLAGS): Guard for coroutine keywords. + * c-cppbuiltin.c (__cpp_coroutines): New cpp define. + * c.opt (fcoroutines): New command-line switch. + 2020-01-10 David Malcolm * c-format.c (local_event_ptr_node): New. diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c index e2d4fd0605a..82c08945a50 100644 --- a/gcc/c-family/c-common.c +++ b/gcc/c-family/c-common.c @@ -537,6 +537,11 @@ const struct c_common_resword c_common_reswords[] = { "concept", RID_CONCEPT, D_CXX_CONCEPTS_FLAGS | D_CXXWARN }, { "requires", RID_REQUIRES, D_CXX_CONCEPTS_FLAGS | D_CXXWARN }, + /* Coroutines-related keywords */ + { "co_await", RID_CO_AWAIT, D_CXX_COROUTINES_FLAGS | D_CXXWARN }, + { "co_yield", RID_CO_YIELD, D_CXX_COROUTINES_FLAGS | D_CXXWARN }, + { "co_return", RID_CO_RETURN, D_CXX_COROUTINES_FLAGS | D_CXXWARN }, + /* These Objective-C keywords are recognized only immediately after an '@'. */ { "compatibility_alias", RID_AT_ALIAS, D_OBJC }, diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h index 37b0594995a..59d4aaf4435 100644 --- a/gcc/c-family/c-common.h +++ b/gcc/c-family/c-common.h @@ -189,6 +189,9 @@ enum rid /* C++ concepts */ RID_CONCEPT, RID_REQUIRES, + /* C++ coroutines */ + RID_CO_AWAIT, RID_CO_YIELD, RID_CO_RETURN, + /* C++ transactional memory. */ RID_ATOMIC_NOEXCEPT, RID_ATOMIC_CANCEL, RID_SYNCHRONIZED, @@ -433,9 +436,11 @@ extern machine_mode c_default_pointer_mode; #define D_TRANSMEM 0X0800 /* C++ transactional memory TS. */ #define D_CXX_CHAR8_T 0X1000 /* In C++, only with -fchar8_t. */ #define D_CXX20 0x2000 /* In C++, C++20 only. */ +#define D_CXX_COROUTINES 0x4000 /* In C++, only with coroutines. */ #define D_CXX_CONCEPTS_FLAGS D_CXXONLY | D_CXX_CONCEPTS #define D_CXX_CHAR8_T_FLAGS D_CXXONLY | D_CXX_CHAR8_T +#define D_CXX_COROUTINES_FLAGS (D_CXXONLY | D_CXX_COROUTINES) /* The reserved keyword table. */ extern const struct c_common_resword c_common_reswords[]; diff --git a/gcc/c-family/c-cppbuiltin.c b/gcc/c-family/c-cppbuiltin.c index cb869415167..a6308921dc9 100644 --- a/gcc/c-family/c-cppbuiltin.c +++ b/gcc/c-family/c-cppbuiltin.c @@ -1017,6 +1017,8 @@ c_cpp_builtins (cpp_reader *pfile) else cpp_define (pfile, "__cpp_concepts=201507L"); } + if (flag_coroutines) + cpp_define (pfile, "__cpp_coroutines=201902L"); /* n4835, C++20 CD */ if (flag_tm) /* Use a value smaller than the 201505 specified in the TS, since we don't yet support atomic_cancel. */ diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 230b76387ba..aa0fa5deae6 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1477,6 +1477,10 @@ fconstexpr-ops-limit= C++ ObjC++ Joined RejectNegative Host_Wide_Int Var(constexpr_ops_limit) Init(33554432) -fconstexpr-ops-limit= Specify maximum number of constexpr operations during a single constexpr evaluation. +fcoroutines +C++ LTO Var(flag_coroutines) +Enable C++ coroutines (experimental). + fdebug-cpp C ObjC C++ ObjC++ Emit debug annotations during preprocessing. diff --git a/gcc/coroutine-builtins.def b/gcc/coroutine-builtins.def new file mode 100644 index 00000000000..3839cb54fae --- /dev/null +++ b/gcc/coroutine-builtins.def @@ -0,0 +1,53 @@ +/* This file contains the definitions and documentation for the + coroutines builtins used in GCC. + + Copyright (C) 2018-2020 Free Software Foundation, Inc. + + Contributed by Iain Sandoe under contract to Facebook. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +/* Before including this file, you should define a macro: + + DEF_BUILTIN_STUB(ENUM, NAME) + DEF_COROUTINE_BUILTIN (ENUM, NAME, TYPE, ATTRS) + + See builtins.def for details. + The builtins are created used by library implementations of C++ + coroutines. */ + +/* This has to come before all the coroutine builtins. */ +DEF_BUILTIN_STUB (BEGIN_COROUTINE_BUILTINS, (const char *) 0) + +/* These are the builtins that are externally-visible and used by the + standard library implementation of the coroutine header. */ + +DEF_COROUTINE_BUILTIN (BUILT_IN_CORO_PROMISE, "promise", + BT_FN_PTR_PTR_CONST_SIZE_BOOL, + ATTR_CONST_NOTHROW_LEAF_LIST) + +DEF_COROUTINE_BUILTIN (BUILT_IN_CORO_RESUME, "resume", BT_FN_VOID_PTR, + ATTR_NULL) + +DEF_COROUTINE_BUILTIN (BUILT_IN_CORO_DESTROY, "destroy", BT_FN_VOID_PTR, + ATTR_NULL) + +DEF_COROUTINE_BUILTIN (BUILT_IN_CORO_DONE, "done", BT_FN_BOOL_PTR, + ATTR_NOTHROW_LEAF_LIST) + +/* This has to come after all the coroutine builtins. */ +DEF_BUILTIN_STUB (END_COROUTINE_BUILTINS, (const char *) 0) diff --git a/gcc/coroutine-passes.cc b/gcc/coroutine-passes.cc new file mode 100644 index 00000000000..d032a392ce6 --- /dev/null +++ b/gcc/coroutine-passes.cc @@ -0,0 +1,532 @@ +/* coroutine expansion and optimisation passes. + + Copyright (C) 2018-2020 Free Software Foundation, Inc. + + Contributed by Iain Sandoe under contract to Facebook. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "target.h" +#include "tree.h" +#include "gimple.h" +#include "tree-pass.h" +#include "ssa.h" +#include "cgraph.h" +#include "pretty-print.h" +#include "diagnostic-core.h" +#include "fold-const.h" +#include "internal-fn.h" +#include "langhooks.h" +#include "gimplify.h" +#include "gimple-iterator.h" +#include "gimplify-me.h" +#include "gimple-walk.h" +#include "gimple-fold.h" +#include "tree-cfg.h" +#include "tree-into-ssa.h" +#include "tree-ssa-propagate.h" +#include "gimple-pretty-print.h" +#include "cfghooks.h" + +/* Here we: + * lower the internal function that implements an exit from scope. + * expand the builtins that are used to implement the library + interfaces to the coroutine frame. */ + +static tree +lower_coro_builtin (gimple_stmt_iterator *gsi, bool *handled_ops_p, + struct walk_stmt_info *wi ATTRIBUTE_UNUSED) +{ + gimple *stmt = gsi_stmt (*gsi); + *handled_ops_p = !gimple_has_substatements (stmt); + + if (gimple_code (stmt) != GIMPLE_CALL) + return NULL_TREE; + + /* This internal function implements an exit from scope without + performing any cleanups; it jumps directly to the label provided. */ + if (gimple_call_internal_p (stmt) + && gimple_call_internal_fn (stmt) == IFN_CO_SUSPN) + { + tree dest = TREE_OPERAND (gimple_call_arg (stmt, 0), 0); + ggoto *g = gimple_build_goto (dest); + gsi_replace (gsi, g, /* do EH */ false); + *handled_ops_p = true; + return NULL_TREE; + } + + tree decl = gimple_call_fndecl (stmt); + if (!decl || !fndecl_built_in_p (decl, BUILT_IN_NORMAL)) + return NULL_TREE; + + /* The remaining builtins implement the library interfaces to the coro + frame. */ + unsigned call_idx = 0; + + switch (DECL_FUNCTION_CODE (decl)) + { + default: + break; + case BUILT_IN_CORO_PROMISE: + { + /* If we are discarding this, then skip it; the function has no + side-effects. */ + tree lhs = gimple_call_lhs (stmt); + if (!lhs) + { + gsi_remove (gsi, true); + *handled_ops_p = true; + return NULL_TREE; + } + /* The coro frame starts with two pointers (to the resume and + destroy() functions). These are followed by the promise which + is aligned as per type [or user attribute]. + The input pointer is the first argument. + The promise alignment is the second and the third is a bool + that is true when we are converting from a promise ptr to a + frame pointer, and false for the inverse. */ + tree ptr = gimple_call_arg (stmt, 0); + tree align_t = gimple_call_arg (stmt, 1); + tree from = gimple_call_arg (stmt, 2); + gcc_checking_assert (TREE_CODE (align_t) == INTEGER_CST); + gcc_checking_assert (TREE_CODE (from) == INTEGER_CST); + bool dir = wi::to_wide (from) != 0; + HOST_WIDE_INT promise_align = TREE_INT_CST_LOW (align_t); + HOST_WIDE_INT psize = + TREE_INT_CST_LOW (TYPE_SIZE_UNIT (ptr_type_node)); + HOST_WIDE_INT align = TYPE_ALIGN_UNIT (ptr_type_node); + align = MAX (align, promise_align); + psize *= 2; /* Start with two pointers. */ + psize = ROUND_UP (psize, align); + HOST_WIDE_INT offs = dir ? -psize : psize; + tree repl = build2 (POINTER_PLUS_EXPR, ptr_type_node, ptr, + size_int (offs)); + gassign *grpl = gimple_build_assign (lhs, repl); + gsi_replace (gsi, grpl, true); + *handled_ops_p = true; + } + break; + case BUILT_IN_CORO_DESTROY: + call_idx = 1; + /* FALLTHROUGH */ + case BUILT_IN_CORO_RESUME: + { + tree ptr = gimple_call_arg (stmt, 0); /* frame ptr. */ + HOST_WIDE_INT psize = + TREE_INT_CST_LOW (TYPE_SIZE_UNIT (ptr_type_node)); + HOST_WIDE_INT offset = call_idx * psize; + tree fntype = TREE_TYPE (decl); + tree fntype_ptr = build_pointer_type (fntype); + tree fntype_ppp = build_pointer_type (fntype_ptr); + tree indirect = fold_build2 (MEM_REF, fntype_ptr, ptr, + build_int_cst (fntype_ppp, offset)); + tree f_ptr_tmp = make_ssa_name (TYPE_MAIN_VARIANT (fntype_ptr)); + gassign *get_fptr = gimple_build_assign (f_ptr_tmp, indirect); + gsi_insert_before (gsi, get_fptr, GSI_SAME_STMT); + gimple_call_set_fn (static_cast (stmt), f_ptr_tmp); + *handled_ops_p = true; + } + break; + case BUILT_IN_CORO_DONE: + { + /* If we are discarding this, then skip it; the function has no + side-effects. */ + tree lhs = gimple_call_lhs (stmt); + if (!lhs) + { + gsi_remove (gsi, true); + *handled_ops_p = true; + return NULL_TREE; + } + /* When we're done, the resume fn is set to NULL. */ + tree ptr = gimple_call_arg (stmt, 0); /* frame ptr. */ + tree vpp = build_pointer_type (ptr_type_node); + tree indirect + = fold_build2 (MEM_REF, vpp, ptr, build_int_cst (vpp, 0)); + tree d_ptr_tmp = make_ssa_name (ptr_type_node); + gassign *get_dptr = gimple_build_assign (d_ptr_tmp, indirect); + gsi_insert_before (gsi, get_dptr, GSI_SAME_STMT); + tree done = fold_build2 (EQ_EXPR, boolean_type_node, d_ptr_tmp, + null_pointer_node); + gassign *get_res = gimple_build_assign (lhs, done); + gsi_replace (gsi, get_res, true); + *handled_ops_p = true; + } + break; + } + return NULL_TREE; +} + +/* Main entry point for lowering coroutine FE builtins. */ + +static unsigned int +execute_lower_coro_builtins (void) +{ + struct walk_stmt_info wi; + gimple_seq body; + + body = gimple_body (current_function_decl); + memset (&wi, 0, sizeof (wi)); + walk_gimple_seq_mod (&body, lower_coro_builtin, NULL, &wi); + gimple_set_body (current_function_decl, body); + + return 0; +} + +namespace { + +const pass_data pass_data_coroutine_lower_builtins = { + GIMPLE_PASS, /* type */ + "coro-lower-builtins", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_NONE, /* tv_id */ + 0, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0 /* todo_flags_finish */ +}; + +class pass_coroutine_lower_builtins : public gimple_opt_pass +{ +public: + pass_coroutine_lower_builtins (gcc::context *ctxt) + : gimple_opt_pass (pass_data_coroutine_lower_builtins, ctxt) + {} + + /* opt_pass methods: */ + virtual bool gate (function *) { return flag_coroutines; }; + + virtual unsigned int execute (function *f ATTRIBUTE_UNUSED) + { + return execute_lower_coro_builtins (); + } + +}; // class pass_coroutine_lower_builtins + +} // namespace + +gimple_opt_pass * +make_pass_coroutine_lower_builtins (gcc::context *ctxt) +{ + return new pass_coroutine_lower_builtins (ctxt); +} + +/* Expand the remaining coroutine IFNs. + + In the front end we construct a single actor function that contains + the coroutine state machine. + + The actor function has three entry conditions: + 1. from the ramp, resume point 0 - to initial-suspend. + 2. when resume () is executed (resume point N). + 3. from the destroy () shim when that is executed. + + The actor function begins with two dispatchers; one for resume and + one for destroy (where the initial entry from the ramp is a special- + case of resume point 0). + + Each suspend point and each dispatch entry is marked with an IFN such + that we can connect the relevant dispatchers to their target labels. + + So, if we have: + + CO_YIELD (NUM, FINAL, RES_LAB, DEST_LAB, FRAME_PTR) + + This is await point NUM, and is the final await if FINAL is non-zero. + The resume point is RES_LAB, and the destroy point is DEST_LAB. + + We expect to find a CO_ACTOR (NUM) in the resume dispatcher and a + CO_ACTOR (NUM+1) in the destroy dispatcher. + + Initially, the intent of keeping the resume and destroy paths together + is that the conditionals controlling them are identical, and thus there + would be duplication of any optimisation of those paths if the split + were earlier. + + Subsequent inlining of the actor (and DCE) is then able to extract the + resume and destroy paths as separate functions if that is found + profitable by the optimisers. + + Once we have remade the connections to their correct postions, we elide + the labels that the front end inserted. */ + +static void +move_edge_and_update (edge e, basic_block old_bb, basic_block new_bb) +{ + if (dump_file) + fprintf (dump_file, "redirecting edge from bb %u to bb %u\n", old_bb->index, + new_bb->index); + + e = redirect_edge_and_branch (e, new_bb); + if (!e && dump_file) + fprintf (dump_file, "failed to redirect edge .. \n"); + + /* Die if we failed. */ + gcc_checking_assert (e); +} + +static unsigned int +execute_early_expand_coro_ifns (void) +{ + /* Don't rebuild stuff unless we have to. */ + unsigned int todoflags = 0; + bool changed = false; + /* Some of the possible YIELD points will hopefully have been removed by + earlier optimisations; record the ones that are still present. */ + hash_map, tree> destinations; + /* Labels we added to carry the CFG changes, we need to remove these to + avoid confusing EH. */ + hash_set to_remove; + /* List of dispatch points to update. */ + auto_vec actor_worklist; + basic_block bb; + gimple_stmt_iterator gsi; + + FOR_EACH_BB_FN (bb, cfun) + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi);) + { + gimple *stmt = gsi_stmt (gsi); + + if (!is_gimple_call (stmt) || !gimple_call_internal_p (stmt)) + { + gsi_next (&gsi); + continue; + } + switch (gimple_call_internal_fn (stmt)) + { + case IFN_CO_FRAME: + { + /* This internal function is a placeholder for the frame + size. In principle, we might lower it later (after some + optimisation had reduced the frame size). At present, + without any such optimisation, we just set it here. */ + tree lhs = gimple_call_lhs (stmt); + tree size = gimple_call_arg (stmt, 0); + /* Right now, this is a trivial operation - copy through + the size computed during initial layout. */ + gassign *grpl = gimple_build_assign (lhs, size); + gsi_replace (&gsi, grpl, true); + gsi_next (&gsi); + } + break; + case IFN_CO_ACTOR: + changed = true; + actor_worklist.safe_push (gsi); /* Save for later. */ + gsi_next (&gsi); + break; + case IFN_CO_YIELD: + { + changed = true; + /* .CO_YIELD (NUM, FINAL, RES_LAB, DEST_LAB, FRAME_PTR); + NUM = await number. + FINAL = 1 if this is the final_suspend() await. + RES_LAB = resume point label. + DEST_LAB = destroy point label. + FRAME_PTR = is a null pointer with the type of the coro + frame, so that we can resize, if needed. */ + if (dump_file) + fprintf (dump_file, "saw CO_YIELD in BB %u\n", bb->index); + tree num = gimple_call_arg (stmt, 0); /* yield point. */ + HOST_WIDE_INT idx = TREE_INT_CST_LOW (num); + bool existed; + tree res_tgt = TREE_OPERAND (gimple_call_arg (stmt, 2), 0); + tree &res_dest = destinations.get_or_insert (idx, &existed); + if (existed && dump_file) + { + fprintf ( + dump_file, + "duplicate YIELD RESUME point (" HOST_WIDE_INT_PRINT_DEC + ") ?\n", + idx); + print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS); + } + else + res_dest = res_tgt; + tree dst_tgt = TREE_OPERAND (gimple_call_arg (stmt, 3), 0); + tree &dst_dest = destinations.get_or_insert (idx + 1, &existed); + if (existed && dump_file) + { + fprintf ( + dump_file, + "duplicate YIELD DESTROY point (" HOST_WIDE_INT_PRINT_DEC + ") ?\n", + idx + 1); + print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS); + } + else + dst_dest = dst_tgt; + to_remove.add (res_tgt); + to_remove.add (dst_tgt); + /* lose the co_yield. */ + gsi_remove (&gsi, true); + stmt = gsi_stmt (gsi); /* next. */ + /* lose the copy present at O0. */ + if (is_gimple_assign (stmt)) + { + gsi_remove (&gsi, true); + stmt = gsi_stmt (gsi); + } + /* Simplify the switch or if following. */ + if (gswitch *gsw = dyn_cast (stmt)) + { + gimple_switch_set_index (gsw, integer_zero_node); + fold_stmt (&gsi); + } + else if (gcond *gif = dyn_cast (stmt)) + { + if (gimple_cond_code (gif) == EQ_EXPR) + gimple_cond_make_true (gif); + else + gimple_cond_make_false (gif); + fold_stmt (&gsi); + } + else if (dump_file) + print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS); + if (gsi_end_p (gsi)) + break; + continue; + } + default: + gsi_next (&gsi); + break; + } + } + + if (!changed) + { + if (dump_file) + fprintf (dump_file, "coro: nothing to do\n"); + return todoflags; + } + + while (!actor_worklist.is_empty ()) + { + gsi = actor_worklist.pop (); + gimple *stmt = gsi_stmt (gsi); + gcc_checking_assert (is_gimple_call (stmt) + && gimple_call_internal_p (stmt) + && gimple_call_internal_fn (stmt) == IFN_CO_ACTOR); + bb = gsi_bb (gsi); + HOST_WIDE_INT idx = TREE_INT_CST_LOW (gimple_call_arg (stmt, 0)); + tree *seen = destinations.get (idx); + changed = true; + + if (dump_file) + fprintf (dump_file, "saw CO_ACTOR in BB %u\n", bb->index); + + if (!seen) + { + /* If we never saw this index, it means that the CO_YIELD + associated was elided during earlier optimisations, so we + don't need to fix up the switch targets. */ + if (dump_file) + fprintf (dump_file, "yield point " HOST_WIDE_INT_PRINT_DEC + " not used, removing it .. \n", idx); + gsi_remove (&gsi, true); + release_defs (stmt); + } + else + { + /* So we need to switch the target of this switch case to the + relevant BB. */ + basic_block new_bb = label_to_block (cfun, *seen); + /* We expect the block we're modifying to contain a single + CO_ACTOR() followed by a goto . */ + gcc_checking_assert (EDGE_COUNT (bb->succs) == 1); + edge e; + edge_iterator ei; + FOR_EACH_EDGE (e, ei, bb->succs) + { + basic_block old_bb = e->dest; + move_edge_and_update (e, old_bb, new_bb); + } + gsi_remove (&gsi, true); + } + } + + /* Remove the labels we inserted to map our hidden CFG, this + avoids confusing block merges when there are also EH labels. */ + FOR_EACH_BB_FN (bb, cfun) + for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi);) + { + gimple *stmt = gsi_stmt (gsi); + if (glabel *glab = dyn_cast (stmt)) + { + tree rem = gimple_label_label (glab); + if (to_remove.contains (rem)) + { + gsi_remove (&gsi, true); + to_remove.remove (rem); + continue; /* We already moved to the next insn. */ + } + } + else + break; + gsi_next (&gsi); + } + + /* Changed the CFG. */ + todoflags |= TODO_cleanup_cfg; + return todoflags; +} + +namespace { + +const pass_data pass_data_coroutine_early_expand_ifns = { + GIMPLE_PASS, /* type */ + "coro-early-expand-ifns", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_NONE, /* tv_id */ + (PROP_cfg), /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + 0 /* todo_flags_finish, set this in the fn. */ +}; + +class pass_coroutine_early_expand_ifns : public gimple_opt_pass +{ +public: + pass_coroutine_early_expand_ifns (gcc::context *ctxt) + : gimple_opt_pass (pass_data_coroutine_early_expand_ifns, ctxt) + {} + + /* opt_pass methods: */ + virtual bool gate (function *f) + { + return flag_coroutines && f->coroutine_component; + } + + virtual unsigned int execute (function *f ATTRIBUTE_UNUSED) + { + return execute_early_expand_coro_ifns (); + } + +}; // class pass_coroutine_expand_ifns + +} // namespace + +gimple_opt_pass * +make_pass_coroutine_early_expand_ifns (gcc::context *ctxt) +{ + return new pass_coroutine_early_expand_ifns (ctxt); +} diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index 62d8acfad5a..90665e1f8e3 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,39 @@ +2020-01-18 Iain Sandoe + + * Make-lang.in: Add coroutines.o. + * cp-tree.h (lang_decl-fn): coroutine_p, new bit. + (DECL_COROUTINE_P): New. + * lex.c (init_reswords): Enable keywords when the coroutine flag + is set, + * operators.def (co_await): New operator. + * call.c (add_builtin_candidates): Handle CO_AWAIT_EXPR. + (op_error): Likewise. + (build_new_op_1): Likewise. + (build_new_function_call): Validate coroutine builtin arguments. + * constexpr.c (potential_constant_expression_1): Handle + CO_AWAIT_EXPR, CO_YIELD_EXPR, CO_RETURN_EXPR. + * coroutines.cc: New file. + * cp-objcp-common.c (cp_common_init_ts): Add CO_AWAIT_EXPR, + CO_YIELD_EXPR, CO_RETRN_EXPR as TS expressions. + * cp-tree.def (CO_AWAIT_EXPR, CO_YIELD_EXPR, (CO_RETURN_EXPR): New. + * cp-tree.h (coro_validate_builtin_call): New. + * decl.c (emit_coro_helper): New. + (finish_function): Handle the case when a function is found to + be a coroutine, perform the outlining and emit the outlined + functions. Set a bit to signal that this is a coroutine component. + * parser.c (enum required_token): New enumeration RT_CO_YIELD. + (cp_parser_unary_expression): Handle co_await. + (cp_parser_assignment_expression): Handle co_yield. + (cp_parser_statement): Handle RID_CO_RETURN. + (cp_parser_jump_statement): Handle co_return. + (cp_parser_operator): Handle co_await operator. + (cp_parser_yield_expression): New. + (cp_parser_required_error): Handle RT_CO_YIELD. + * pt.c (tsubst_copy): Handle CO_AWAIT_EXPR. + (tsubst_expr): Handle CO_AWAIT_EXPR, CO_YIELD_EXPR and + CO_RETURN_EXPRs. + * tree.c (cp_walk_subtrees): Likewise. + 2020-01-17 Jason Merrill PR c++/92531 - ICE with noexcept(lambda). diff --git a/gcc/cp/Make-lang.in b/gcc/cp/Make-lang.in index d94afa44a62..7896591dd4b 100644 --- a/gcc/cp/Make-lang.in +++ b/gcc/cp/Make-lang.in @@ -73,7 +73,7 @@ CXX_C_OBJS = attribs.o incpath.o \ # Language-specific object files for C++ and Objective C++. CXX_AND_OBJCXX_OBJS = \ cp/call.o cp/class.o cp/constexpr.o cp/constraint.o \ - cp/cp-gimplify.o \ + cp/coroutines.o cp/cp-gimplify.o \ cp/cp-objcp-common.o cp/cp-ubsan.o \ cp/cvt.o cp/cxx-pretty-print.o \ cp/decl.o cp/decl2.o cp/dump.o \ diff --git a/gcc/cp/call.c b/gcc/cp/call.c index 32ccfc973e4..d47747117b9 100644 --- a/gcc/cp/call.c +++ b/gcc/cp/call.c @@ -3173,6 +3173,7 @@ add_builtin_candidates (struct z_candidate **candidates, enum tree_code code, case ADDR_EXPR: case COMPOUND_EXPR: case COMPONENT_REF: + case CO_AWAIT_EXPR: return; case COND_EXPR: @@ -4584,6 +4585,13 @@ build_new_function_call (tree fn, vec **args, result = build_over_call (cand, flags, complain); } + if (flag_coroutines + && result + && TREE_CODE (result) == CALL_EXPR + && DECL_BUILT_IN_CLASS (TREE_OPERAND (CALL_EXPR_FN (result), 0)) + == BUILT_IN_NORMAL) + result = coro_validate_builtin_call (result); + /* Free all the conversions we allocated. */ obstack_free (&conversion_obstack, p); @@ -4942,6 +4950,16 @@ op_error (const op_location_t &loc, opname, opname, arg1, TREE_TYPE (arg1)); break; + case CO_AWAIT_EXPR: + if (flag_diagnostics_show_caret) + error_at (loc, op_error_string (G_("%"), 1, match), + opname, TREE_TYPE (arg1)); + else + error_at (loc, op_error_string (G_("% in %<%s%E%>"), + 1, match), + opname, opname, arg1, TREE_TYPE (arg1)); + break; + default: if (arg2) if (flag_diagnostics_show_caret) @@ -6197,6 +6215,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags, case ADDR_EXPR: case COMPOUND_EXPR: case COMPONENT_REF: + case CO_AWAIT_EXPR: result = NULL_TREE; result_valid_p = true; break; @@ -6489,6 +6508,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags, case REALPART_EXPR: case IMAGPART_EXPR: case ABS_EXPR: + case CO_AWAIT_EXPR: return cp_build_unary_op (code, arg1, false, complain); case ARRAY_REF: diff --git a/gcc/cp/config-lang.in b/gcc/cp/config-lang.in index 47b11df0b27..da70b358413 100644 --- a/gcc/cp/config-lang.in +++ b/gcc/cp/config-lang.in @@ -39,7 +39,7 @@ gtfiles="\ \$(srcdir)/c-family/c-common.c \$(srcdir)/c-family/c-format.c \ \$(srcdir)/c-family/c-cppbuiltin.c \$(srcdir)/c-family/c-pragma.c \ \$(srcdir)/cp/call.c \$(srcdir)/cp/class.c \$(srcdir)/cp/constexpr.c \ -\$(srcdir)/cp/constraint.cc \ +\$(srcdir)/cp/constraint.cc \$(srcdir)/cp/coroutines.cc \ \$(srcdir)/cp/cp-gimplify.c \ \$(srcdir)/cp/cp-lang.c \$(srcdir)/cp/cp-objcp-common.c \ \$(srcdir)/cp/decl.c \$(srcdir)/cp/decl2.c \ diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index 17b04d7b055..5864b67d4de 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -7852,6 +7852,12 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, case ANNOTATE_EXPR: return RECUR (TREE_OPERAND (t, 0), rval); + /* Coroutine await, yield and return expressions are not. */ + case CO_AWAIT_EXPR: + case CO_YIELD_EXPR: + case CO_RETURN_EXPR: + return false; + default: if (objc_is_property_ref (t)) return false; diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc new file mode 100644 index 00000000000..ad0e9efa2b7 --- /dev/null +++ b/gcc/cp/coroutines.cc @@ -0,0 +1,3643 @@ +/* coroutine-specific state, expansions and tests. + + Copyright (C) 2018-2020 Free Software Foundation, Inc. + + Contributed by Iain Sandoe under contract to Facebook. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "target.h" +#include "cp-tree.h" +#include "stringpool.h" +#include "stmt.h" +#include "stor-layout.h" +#include "tree-iterator.h" +#include "tree.h" +#include "gcc-rich-location.h" +#include "hash-map.h" + +static tree find_promise_type (tree); +static bool coro_promise_type_found_p (tree, location_t); + +/* GCC C++ coroutines implementation. + + The user authors a function that becomes a coroutine (lazily) by + making use of any of the co_await, co_yield or co_return keywords. + + Unlike a regular function, where the activation record is placed on the + stack, and is destroyed on function exit, a coroutine has some state that + persists between calls - the coroutine frame (analogous to a stack frame). + + We transform the user's function into three pieces: + 1. A so-called ramp function, that establishes the coroutine frame and + begins execution of the coroutine. + 2. An actor function that contains the state machine corresponding to the + user's suspend/resume structure. + 3. A stub function that calls the actor function in 'destroy' mode. + + The actor function is executed: + * from "resume point 0" by the ramp. + * from resume point N ( > 0 ) for handle.resume() calls. + * from the destroy stub for destroy point N for handle.destroy() calls. + + The functions in this file carry out the necessary analysis of, and + transforms to, the AST to perform this. + + The C++ coroutine design makes use of some helper functions that are + authored in a so-called "promise" class provided by the user. + + At parse time (or post substitution) the type of the coroutine promise + will be determined. At that point, we can look up the required promise + class methods and issue diagnostics if they are missing or incorrect. To + avoid repeating these actions at code-gen time, we make use of temporary + 'proxy' variables for the coroutine handle and the promise - which will + eventually be instantiated in the coroutine frame. + + Each of the keywords will expand to a code sequence (although co_yield is + just syntactic sugar for a co_await). + + We defer the analysis and transformation until template expansion is + complete so that we have complete types at that time. */ + + +/* The state that we collect during parsing (and template expansion) for + a coroutine. */ + +struct GTY((for_user)) coroutine_info +{ + tree function_decl; /* The original function decl. */ + tree promise_type; /* The cached promise type for this function. */ + tree handle_type; /* The cached coroutine handle for this function. */ + tree self_h_proxy; /* A handle instance that is used as the proxy for the + one that will eventually be allocated in the coroutine + frame. */ + tree promise_proxy; /* Likewise, a proxy promise instance. */ + location_t first_coro_keyword; /* The location of the keyword that made this + function into a coroutine. */ +}; + +struct coroutine_info_hasher : ggc_ptr_hash +{ + typedef tree compare_type; /* We only compare the function decl. */ + static inline hashval_t hash (coroutine_info *); + static inline hashval_t hash (const compare_type &); + static inline bool equal (coroutine_info *, coroutine_info *); + static inline bool equal (coroutine_info *, const compare_type &); +}; + +/* This table holds all the collected coroutine state for coroutines in + the current translation unit. */ + +static GTY (()) hash_table *coroutine_info_table; + +/* We will initialise state lazily. */ +static bool coro_initialized = false; + +/* Return a hash value for the entry pointed to by INFO. + The compare type is a tree, but the only trees we are going use are + function decls. We use the DECL_UID as the hash value since that is + stable across PCH. */ + +hashval_t +coroutine_info_hasher::hash (coroutine_info *info) +{ + return DECL_UID (info->function_decl); +} + +/* Return a hash value for the compare value COMP. */ + +hashval_t +coroutine_info_hasher::hash (const compare_type& comp) +{ + return DECL_UID (comp); +} + +/* Return true if the entries pointed to by LHS and RHS are for the + same coroutine. */ + +bool +coroutine_info_hasher::equal (coroutine_info *lhs, coroutine_info *rhs) +{ + return lhs->function_decl == rhs->function_decl; +} + +bool +coroutine_info_hasher::equal (coroutine_info *lhs, const compare_type& rhs) +{ + return lhs->function_decl == rhs; +} + +/* Get the existing coroutine_info for FN_DECL, or insert a new one if the + entry does not yet exist. */ + +coroutine_info * +get_or_insert_coroutine_info (tree fn_decl) +{ + gcc_checking_assert (coroutine_info_table != NULL); + + coroutine_info **slot = coroutine_info_table->find_slot_with_hash + (fn_decl, coroutine_info_hasher::hash (fn_decl), INSERT); + + if (*slot == NULL) + { + *slot = new (ggc_cleared_alloc ()) coroutine_info (); + (*slot)->function_decl = fn_decl; + } + + return *slot; +} + +/* Get the existing coroutine_info for FN_DECL, fail if it doesn't exist. */ + +coroutine_info * +get_coroutine_info (tree fn_decl) +{ + gcc_checking_assert (coroutine_info_table != NULL); + + coroutine_info **slot = coroutine_info_table->find_slot_with_hash + (fn_decl, coroutine_info_hasher::hash (fn_decl), NO_INSERT); + if (slot) + return *slot; + return NULL; +} + +/* We will lazily create all the identifiers that are used by coroutines + on the first attempt to lookup the traits. */ + +/* Identifiers that are used by all coroutines. */ + +static GTY(()) tree coro_traits_identifier; +static GTY(()) tree coro_handle_identifier; +static GTY(()) tree coro_promise_type_identifier; + +/* Required promise method name identifiers. */ + +static GTY(()) tree coro_await_transform_identifier; +static GTY(()) tree coro_initial_suspend_identifier; +static GTY(()) tree coro_final_suspend_identifier; +static GTY(()) tree coro_return_void_identifier; +static GTY(()) tree coro_return_value_identifier; +static GTY(()) tree coro_yield_value_identifier; +static GTY(()) tree coro_resume_identifier; +static GTY(()) tree coro_from_address_identifier; +static GTY(()) tree coro_get_return_object_identifier; +static GTY(()) tree coro_gro_on_allocation_fail_identifier; +static GTY(()) tree coro_unhandled_exception_identifier; + +/* Awaitable methods. */ + +static GTY(()) tree coro_await_ready_identifier; +static GTY(()) tree coro_await_suspend_identifier; +static GTY(()) tree coro_await_resume_identifier; + +/* Create the identifiers used by the coroutines library interfaces. */ + +static void +coro_init_identifiers () +{ + coro_traits_identifier = get_identifier ("coroutine_traits"); + coro_handle_identifier = get_identifier ("coroutine_handle"); + coro_promise_type_identifier = get_identifier ("promise_type"); + + coro_await_transform_identifier = get_identifier ("await_transform"); + coro_initial_suspend_identifier = get_identifier ("initial_suspend"); + coro_final_suspend_identifier = get_identifier ("final_suspend"); + coro_return_void_identifier = get_identifier ("return_void"); + coro_return_value_identifier = get_identifier ("return_value"); + coro_yield_value_identifier = get_identifier ("yield_value"); + coro_resume_identifier = get_identifier ("resume"); + coro_from_address_identifier = get_identifier ("from_address"); + coro_get_return_object_identifier = get_identifier ("get_return_object"); + coro_gro_on_allocation_fail_identifier = + get_identifier ("get_return_object_on_allocation_failure"); + coro_unhandled_exception_identifier = get_identifier ("unhandled_exception"); + + coro_await_ready_identifier = get_identifier ("await_ready"); + coro_await_suspend_identifier = get_identifier ("await_suspend"); + coro_await_resume_identifier = get_identifier ("await_resume"); +} + +/* Trees we only need to set up once. */ + +static GTY(()) tree coro_traits_templ; +static GTY(()) tree coro_handle_templ; +static GTY(()) tree void_coro_handle_type; + +/* ================= Parse, Semantics and Type checking ================= */ + +/* This initial set of routines are helper for the parsing and template + expansion phases. + + At the completion of this, we will have completed trees for each of the + keywords, but making use of proxy variables for the self-handle and the + promise class instance. */ + +/* [coroutine.traits] + Lookup the coroutine_traits template decl. */ + +static tree +find_coro_traits_template_decl (location_t kw) +{ + tree traits_decl = lookup_qualified_name (std_node, coro_traits_identifier, + 0, true); + if (traits_decl == NULL_TREE || traits_decl == error_mark_node) + { + error_at (kw, "cannot find % template"); + return NULL_TREE; + } + else + return traits_decl; +} + +/* Instantiate Coroutine traits for the function signature. */ + +static tree +instantiate_coro_traits (tree fndecl, location_t kw) +{ + /* [coroutine.traits.primary] + So now build up a type list for the template . + The types are the function's arg types and _R is the function return + type. */ + + tree functyp = TREE_TYPE (fndecl); + tree arg_node = TYPE_ARG_TYPES (functyp); + tree argtypes = make_tree_vec (list_length (arg_node)-1); + unsigned p = 0; + + while (arg_node != NULL_TREE && !VOID_TYPE_P (TREE_VALUE (arg_node))) + { + TREE_VEC_ELT (argtypes, p++) = TREE_VALUE (arg_node); + arg_node = TREE_CHAIN (arg_node); + } + + tree argtypepack = cxx_make_type (TYPE_ARGUMENT_PACK); + SET_ARGUMENT_PACK_ARGS (argtypepack, argtypes); + + tree targ = make_tree_vec (2); + TREE_VEC_ELT (targ, 0) = TREE_TYPE (functyp); + TREE_VEC_ELT (targ, 1) = argtypepack; + + tree traits_class + = lookup_template_class (coro_traits_templ, targ, + /*in_decl=*/ NULL_TREE, + /*context=*/ NULL_TREE /*std_node*/, + /*entering scope=*/ false, tf_warning_or_error); + + if (traits_class == error_mark_node || traits_class == NULL_TREE) + { + error_at (kw, "cannot instantiate %"); + return NULL_TREE; + } + + return traits_class; +} + +/* [coroutine.handle] */ + +static tree +find_coro_handle_template_decl (location_t kw) +{ + tree handle_decl = lookup_qualified_name (std_node, coro_handle_identifier, + 0, true); + if (handle_decl == NULL_TREE || handle_decl == error_mark_node) + { + error_at (kw, "cannot find % template"); + return NULL_TREE; + } + else + return handle_decl; +} + +/* Instantiate the handle template for a given promise type. */ + +static tree +instantiate_coro_handle_for_promise_type (location_t kw, tree promise_type) +{ + /* So now build up a type list for the template, one entry, the promise. */ + tree targ = make_tree_vec (1); + TREE_VEC_ELT (targ, 0) = promise_type; + tree handle_type + = lookup_template_class (coro_handle_identifier, targ, + /* in_decl */ NULL_TREE, + /* context */ std_node, + /* entering scope */ false, tf_warning_or_error); + + if (handle_type == error_mark_node) + { + error_at (kw, "cannot instantiate a % for" + " promise type %qT", promise_type); + return NULL_TREE; + } + + return handle_type; +} + +/* Look for the promise_type in the instantiated traits. */ + +static tree +find_promise_type (tree traits_class) +{ + tree promise_type + = lookup_member (traits_class, coro_promise_type_identifier, + /* protect */ 1, /*want_type=*/true, tf_warning_or_error); + + if (promise_type) + promise_type + = complete_type_or_else (TREE_TYPE (promise_type), promise_type); + + /* NULL_TREE on fail. */ + return promise_type; +} + +static bool +coro_promise_type_found_p (tree fndecl, location_t loc) +{ + gcc_assert (fndecl != NULL_TREE); + + /* Save the coroutine data on the side to avoid the overhead on every + function decl. */ + + /* We only need one entry per coroutine in a TU, the assumption here is that + there are typically not 1000s. */ + if (!coro_initialized) + { + gcc_checking_assert (coroutine_info_table == NULL); + /* A table to hold the state, per coroutine decl. */ + coroutine_info_table = + hash_table::create_ggc (11); + /* Set up the identifiers we will use. */ + gcc_checking_assert (coro_traits_identifier == NULL); + coro_init_identifiers (); + /* Trees we only need to create once. */ + /* Coroutine traits template. */ + coro_traits_templ = find_coro_traits_template_decl (loc); + gcc_checking_assert (coro_traits_templ != NULL); + /* coroutine_handle<> template. */ + coro_handle_templ = find_coro_handle_template_decl (loc); + gcc_checking_assert (coro_handle_templ != NULL); + /* We can also instantiate the void coroutine_handle<> */ + void_coro_handle_type = + instantiate_coro_handle_for_promise_type (loc, NULL_TREE); + gcc_checking_assert (void_coro_handle_type != NULL); + coro_initialized = true; + } + + coroutine_info *coro_info = get_or_insert_coroutine_info (fndecl); + /* Without this, we cannot really proceed. */ + gcc_checking_assert (coro_info); + + /* If we don't already have a current promise type, try to look it up. */ + if (coro_info->promise_type == NULL_TREE) + { + /* Get the coroutine traits template class instance for the function + signature we have - coroutine_traits */ + tree templ_class = instantiate_coro_traits (fndecl, loc); + + /* Find the promise type for that. */ + coro_info->promise_type = find_promise_type (templ_class); + + /* If we don't find it, punt on the rest. */ + if (coro_info->promise_type == NULL_TREE) + { + error_at (loc, "unable to find the promise type for this coroutine"); + return false; + } + + /* Try to find the handle type for the promise. */ + tree handle_type = + instantiate_coro_handle_for_promise_type (loc, coro_info->promise_type); + if (handle_type == NULL_TREE) + return false; + + /* Complete this, we're going to use it. */ + coro_info->handle_type = complete_type_or_else (handle_type, fndecl); + /* Diagnostic would be emitted by complete_type_or_else. */ + if (coro_info->handle_type == error_mark_node) + return false; + + /* Build a proxy for a handle to "self" as the param to + await_suspend() calls. */ + coro_info->self_h_proxy + = build_lang_decl (VAR_DECL, get_identifier ("self_h.proxy"), + coro_info->handle_type); + + /* Build a proxy for the promise so that we can perform lookups. */ + coro_info->promise_proxy + = build_lang_decl (VAR_DECL, get_identifier ("promise.proxy"), + coro_info->promise_type); + + /* Note where we first saw a coroutine keyword. */ + coro_info->first_coro_keyword = loc; + } + + return true; +} + +/* These functions assumes that the caller has verified that the state for + the decl has been initialized, we try to minimize work here. */ + +static tree +get_coroutine_promise_type (tree decl) +{ + if (coroutine_info *info = get_coroutine_info (decl)) + return info->promise_type; + + return NULL_TREE; +} + +static tree +get_coroutine_handle_type (tree decl) +{ + if (coroutine_info *info = get_coroutine_info (decl)) + return info->handle_type; + + return NULL_TREE; +} + +static tree +get_coroutine_self_handle_proxy (tree decl) +{ + if (coroutine_info *info = get_coroutine_info (decl)) + return info->self_h_proxy; + + return NULL_TREE; +} + +static tree +get_coroutine_promise_proxy (tree decl) +{ + if (coroutine_info *info = get_coroutine_info (decl)) + return info->promise_proxy; + + return NULL_TREE; +} + +static tree +lookup_promise_method (tree fndecl, tree member_id, location_t loc, + bool musthave) +{ + tree promise = get_coroutine_promise_type (fndecl); + tree pm_memb + = lookup_member (promise, member_id, + /*protect*/ 1, /*want_type*/ 0, tf_warning_or_error); + if (musthave && (pm_memb == NULL_TREE || pm_memb == error_mark_node)) + { + error_at (loc, "no member named %qE in %qT", member_id, promise); + return error_mark_node; + } + return pm_memb; +} + +/* Here we check the constraints that are common to all keywords (since the + presence of a coroutine keyword makes the function into a coroutine). */ + +static bool +coro_common_keyword_context_valid_p (tree fndecl, location_t kw_loc, + const char *kw_name) +{ + if (fndecl == NULL_TREE) + { + error_at (kw_loc, "%qs cannot be used outside a function", kw_name); + return false; + } + + /* This is arranged in order of prohibitions in the std. */ + if (DECL_MAIN_P (fndecl)) + { + // [basic.start.main] 3. The function main shall not be a coroutine. + error_at (kw_loc, "%qs cannot be used in the % function", + kw_name); + return false; + } + + if (DECL_DECLARED_CONSTEXPR_P (fndecl)) + { + // [dcl.constexpr] 3.3 it shall not be a coroutine. + error_at (kw_loc, "%qs cannot be used in a % function", + kw_name); + cp_function_chain->invalid_constexpr = true; + return false; + } + + if (FNDECL_USED_AUTO (fndecl)) + { + // [dcl.spec.auto] 15. A function declared with a return type that uses + // a placeholder type shall not be a coroutine . + error_at (kw_loc, + "%qs cannot be used in a function with a deduced return type", + kw_name); + return false; + } + + if (varargs_function_p (fndecl)) + { + // [dcl.fct.def.coroutine] The parameter-declaration-clause of the + // coroutine shall not terminate with an ellipsis that is not part + // of a parameter-declaration. + error_at (kw_loc, + "%qs cannot be used in a varargs function", kw_name); + return false; + } + + if (DECL_CONSTRUCTOR_P (fndecl)) + { + // [class.ctor] 7. a constructor shall not be a coroutine. + error_at (kw_loc, "%qs cannot be used in a constructor", kw_name); + return false; + } + + if (DECL_DESTRUCTOR_P (fndecl)) + { + // [class.dtor] 21. a destructor shall not be a coroutine. + error_at (kw_loc, "%qs cannot be used in a destructor", kw_name); + return false; + } + + return true; +} + +/* Here we check the constraints that are not per keyword. */ + +static bool +coro_function_valid_p (tree fndecl) +{ + location_t f_loc = DECL_SOURCE_LOCATION (fndecl); + + /* Since we think the function is a coroutine, that implies we parsed + a keyword that triggered this. Keywords check promise validity for + their context and thus the promise type should be known at this point. */ + gcc_checking_assert (get_coroutine_handle_type (fndecl) != NULL_TREE + && get_coroutine_promise_type (fndecl) != NULL_TREE); + + if (current_function_returns_value || current_function_returns_null) + { + /* TODO: record or extract positions of returns (and the first coro + keyword) so that we can add notes to the diagnostic about where + the bad keyword is and what made the function into a coro. */ + error_at (f_loc, "a % statement is not allowed in coroutine;" + " did you mean %?"); + return false; + } + + return true; +} + +enum suspend_point_kind { + CO_AWAIT_SUSPEND_POINT = 0, + CO_YIELD_SUSPEND_POINT, + INITIAL_SUSPEND_POINT, + FINAL_SUSPEND_POINT +}; + +/* This performs [expr.await] bullet 3.3 and validates the interface obtained. + It is also used to build the initial and final suspend points. + + 'a', 'o' and 'e' are used as per the description in the section noted. + + A, the original yield/await expr, is found at source location LOC. + + We will be constructing a CO_AWAIT_EXPR for a suspend point of one of + the four suspend_point_kind kinds. This is indicated by SUSPEND_KIND. */ + +static tree +build_co_await (location_t loc, tree a, suspend_point_kind suspend_kind) +{ + /* Try and overload of operator co_await, .... */ + tree o; + if (MAYBE_CLASS_TYPE_P (TREE_TYPE (a))) + { + tree overload = NULL_TREE; + o = build_new_op (loc, CO_AWAIT_EXPR, LOOKUP_NORMAL, a, NULL_TREE, + NULL_TREE, &overload, tf_warning_or_error); + /* If no viable functions are found, o is a. */ + if (!o || o == error_mark_node) + o = a; + } + else + o = a; /* This is most likely about to fail anyway. */ + + tree o_type = complete_type_or_else (TREE_TYPE (o), o); + if (TREE_CODE (o_type) != RECORD_TYPE) + { + error_at (loc, "awaitable type %qT is not a structure", + o_type); + return error_mark_node; + } + + /* Check for required awaitable members and their types. */ + tree awrd_meth + = lookup_member (o_type, coro_await_ready_identifier, + /* protect */ 1, /*want_type=*/0, tf_warning_or_error); + + if (!awrd_meth || awrd_meth == error_mark_node) + return error_mark_node; + + tree awsp_meth + = lookup_member (o_type, coro_await_suspend_identifier, + /* protect */ 1, /*want_type=*/0, tf_warning_or_error); + + if (!awsp_meth || awsp_meth == error_mark_node) + return error_mark_node; + + /* The type of the co_await is the return type of the awaitable's + co_resume(), so we need to look that up. */ + tree awrs_meth + = lookup_member (o_type, coro_await_resume_identifier, + /* protect */ 1, /*want_type=*/0, tf_warning_or_error); + + if (!awrs_meth || awrs_meth == error_mark_node) + return error_mark_node; + + /* To complete the lookups, we need an instance of 'e' which is built from + 'o' according to [expr.await] 3.4. However, we don't want to materialize + 'e' here (it might need to be placed in the coroutine frame) so we will + make a temp placeholder instead. */ + tree e_proxy = build_lang_decl (VAR_DECL, NULL_TREE, o_type); + + /* I suppose we could check that this is contextually convertible to bool. */ + tree awrd_func = NULL_TREE; + tree awrd_call + = build_new_method_call (e_proxy, awrd_meth, NULL, NULL_TREE, LOOKUP_NORMAL, + &awrd_func, tf_warning_or_error); + + if (!awrd_func || !awrd_call || awrd_call == error_mark_node) + return error_mark_node; + + /* The suspend method may return one of three types: + 1. void (no special action needed). + 2. bool (if true, we don't need to suspend). + 3. a coroutine handle, we execute the handle.resume() call. */ + tree awsp_func = NULL_TREE; + tree h_proxy = get_coroutine_self_handle_proxy (current_function_decl); + vec *args = make_tree_vector_single (h_proxy); + tree awsp_call + = build_new_method_call (e_proxy, awsp_meth, &args, NULL_TREE, + LOOKUP_NORMAL, &awsp_func, tf_warning_or_error); + + release_tree_vector (args); + if (!awsp_func || !awsp_call || awsp_call == error_mark_node) + return error_mark_node; + + bool ok = false; + tree susp_return_type = TREE_TYPE (TREE_TYPE (awsp_func)); + if (same_type_p (susp_return_type, void_type_node)) + ok = true; + else if (same_type_p (susp_return_type, boolean_type_node)) + ok = true; + else if (TREE_CODE (susp_return_type) == RECORD_TYPE + && CLASS_TYPE_P (susp_return_type)) + { + tree tt = CLASSTYPE_TI_TEMPLATE (susp_return_type); + if (tt == coro_handle_templ) + ok = true; + } + + if (!ok) + { + error_at (loc, "% must return %, % or" + " a coroutine handle"); + return error_mark_node; + } + + /* Finally, the type of e.await_resume() is the co_await's type. */ + tree awrs_func = NULL_TREE; + tree awrs_call + = build_new_method_call (e_proxy, awrs_meth, NULL, NULL_TREE, LOOKUP_NORMAL, + &awrs_func, tf_warning_or_error); + + if (!awrs_func || !awrs_call || awrs_call == error_mark_node) + return error_mark_node; + + /* We now have three call expressions, in terms of the promise, handle and + 'e' proxies. Save them in the await expression for later expansion. */ + + tree awaiter_calls = make_tree_vec (3); + TREE_VEC_ELT (awaiter_calls, 0) = awrd_call; /* await_ready(). */ + TREE_VEC_ELT (awaiter_calls, 1) = awsp_call; /* await_suspend(). */ + TREE_VEC_ELT (awaiter_calls, 2) = awrs_call; /* await_resume(). */ + + return build5_loc (loc, CO_AWAIT_EXPR, TREE_TYPE (awrs_call), a, + e_proxy, o, awaiter_calls, + build_int_cst (integer_type_node, (int) suspend_kind)); +} + +tree +finish_co_await_expr (location_t kw, tree expr) +{ + if (!expr || error_operand_p (expr)) + return error_mark_node; + + if (!coro_common_keyword_context_valid_p (current_function_decl, kw, + "co_await")) + return error_mark_node; + + /* The current function has now become a coroutine, if it wasn't already. */ + DECL_COROUTINE_P (current_function_decl) = 1; + + if (processing_template_decl) + { + if (check_for_bare_parameter_packs (expr)) + return error_mark_node; + + /* If we don't know the promise type, we can't proceed. */ + tree functype = TREE_TYPE (current_function_decl); + if (dependent_type_p (functype) || type_dependent_expression_p (expr)) + return build5_loc (kw, CO_AWAIT_EXPR, TREE_TYPE (expr), expr, NULL_TREE, + NULL_TREE, NULL_TREE, integer_zero_node); + } + + /* We must be able to look up the "await_transform" method in the scope of + the promise type, and obtain its return type. */ + if (!coro_promise_type_found_p (current_function_decl, kw)) + return error_mark_node; + + /* [expr.await] 3.2 + The incoming cast expression might be transformed by a promise + 'await_transform()'. */ + tree at_meth + = lookup_promise_method (current_function_decl, + coro_await_transform_identifier, kw, + /*musthave=*/ false); + if (at_meth == error_mark_node) + return error_mark_node; + + tree a = expr; + if (at_meth) + { + /* try to build a = p.await_transform (e). */ + tree at_fn = NULL_TREE; + vec *args = make_tree_vector_single (expr); + a = build_new_method_call (get_coroutine_promise_proxy ( + current_function_decl), + at_meth, &args, NULL_TREE, LOOKUP_NORMAL, + &at_fn, tf_warning_or_error); + + /* As I read the section. + We saw an await_transform method, so it's mandatory that we replace + expr with p.await_transform (expr), therefore if the method call fails + (presumably, we don't have suitable arguments) then this part of the + process fails. */ + if (!at_fn || a == error_mark_node) + return error_mark_node; + } + + /* Now we want to build co_await a. */ + tree op = build_co_await (kw, a, CO_AWAIT_SUSPEND_POINT); + TREE_SIDE_EFFECTS (op) = 1; + SET_EXPR_LOCATION (op, kw); + + return op; +} + +/* Take the EXPR given and attempt to build: + co_await p.yield_value (expr); + per [expr.yield] para 1. */ + +tree +finish_co_yield_expr (location_t kw, tree expr) +{ + if (!expr || error_operand_p (expr)) + return error_mark_node; + + /* Check the general requirements and simple syntax errors. */ + if (!coro_common_keyword_context_valid_p (current_function_decl, kw, + "co_yield")) + return error_mark_node; + + /* The current function has now become a coroutine, if it wasn't already. */ + DECL_COROUTINE_P (current_function_decl) = 1; + + if (processing_template_decl) + { + if (check_for_bare_parameter_packs (expr)) + return error_mark_node; + + tree functype = TREE_TYPE (current_function_decl); + /* If we don't know the promise type, we can't proceed. */ + if (dependent_type_p (functype) || type_dependent_expression_p (expr)) + return build2_loc (kw, CO_YIELD_EXPR, TREE_TYPE (expr), expr, + NULL_TREE); + } + + if (!coro_promise_type_found_p (current_function_decl, kw)) + /* We must be able to look up the "yield_value" method in the scope of + the promise type, and obtain its return type. */ + return error_mark_node; + + /* The incoming expr is "e" per [expr.yield] para 1, lookup and build a + call for p.yield_value(e). */ + tree y_meth = lookup_promise_method (current_function_decl, + coro_yield_value_identifier, kw, + /*musthave=*/ true); + if (!y_meth || y_meth == error_mark_node) + return error_mark_node; + + tree yield_fn = NULL_TREE; + vec *args = make_tree_vector_single (expr); + tree yield_call = build_new_method_call ( + get_coroutine_promise_proxy (current_function_decl), y_meth, &args, + NULL_TREE, LOOKUP_NORMAL, &yield_fn, tf_warning_or_error); + + if (!yield_fn || yield_call == error_mark_node) + return error_mark_node; + + /* So now we have the type of p.yield_value (e). + Now we want to build co_await p.yield_value (e). + Noting that for co_yield, there is no evaluation of any potential + promise transform_await(). */ + + tree op = build_co_await (kw, yield_call, CO_YIELD_SUSPEND_POINT); + + op = build2_loc (kw, CO_YIELD_EXPR, TREE_TYPE (op), expr, op); + TREE_SIDE_EFFECTS (op) = 1; + + return op; +} + +/* Check that it's valid to have a co_return keyword here. + If it is, then check and build the p.return_{void(),value(expr)}. + These are built against the promise proxy, but saved for expand time. */ + +tree +finish_co_return_stmt (location_t kw, tree expr) +{ + if (expr == error_mark_node) + return error_mark_node; + + if (!coro_common_keyword_context_valid_p (current_function_decl, kw, + "co_return")) + return error_mark_node; + + /* The current function has now become a coroutine, if it wasn't + already. */ + DECL_COROUTINE_P (current_function_decl) = 1; + + if (processing_template_decl) + { + current_function_returns_value = 1; + + if (check_for_bare_parameter_packs (expr)) + return error_mark_node; + + tree functype = TREE_TYPE (current_function_decl); + /* If we don't know the promise type, we can't proceed, return the + expression as it is. */ + if (dependent_type_p (functype) || type_dependent_expression_p (expr)) + { + expr + = build2_loc (kw, CO_RETURN_EXPR, void_type_node, expr, NULL_TREE); + expr = maybe_cleanup_point_expr_void (expr); + expr = add_stmt (expr); + return expr; + } + } + + if (!coro_promise_type_found_p (current_function_decl, kw)) + return error_mark_node; + + if (error_operand_p (expr)) + return error_mark_node; + + /* Suppress -Wreturn-type for co_return, we need to check indirectly + whether the promise type has a suitable return_void/return_value. */ + TREE_NO_WARNING (current_function_decl) = true; + + if (!processing_template_decl && warn_sequence_point) + verify_sequence_points (expr); + + /* If the promise object doesn't have the correct return call then + there's a mis-match between the co_return and this. */ + tree co_ret_call = NULL_TREE; + if (expr == NULL_TREE || VOID_TYPE_P (TREE_TYPE (expr))) + { + tree crv_meth + = lookup_promise_method (current_function_decl, + coro_return_void_identifier, kw, + /*musthave=*/ true); + if (!crv_meth || crv_meth == error_mark_node) + return error_mark_node; + + co_ret_call = build_new_method_call ( + get_coroutine_promise_proxy (current_function_decl), crv_meth, NULL, + NULL_TREE, LOOKUP_NORMAL, NULL, tf_warning_or_error); + } + else + { + tree crv_meth + = lookup_promise_method (current_function_decl, + coro_return_value_identifier, kw, + /*musthave=*/ true); + if (!crv_meth || crv_meth == error_mark_node) + return error_mark_node; + + vec *args = make_tree_vector_single (expr); + co_ret_call = build_new_method_call ( + get_coroutine_promise_proxy (current_function_decl), crv_meth, &args, + NULL_TREE, LOOKUP_NORMAL, NULL, tf_warning_or_error); + } + + /* Makes no sense for a co-routine really. */ + if (TREE_THIS_VOLATILE (current_function_decl)) + warning_at (kw, 0, + "function declared % has a" + " % statement"); + + if (!co_ret_call || co_ret_call == error_mark_node) + return error_mark_node; + + expr = build2_loc (kw, CO_RETURN_EXPR, void_type_node, expr, co_ret_call); + expr = maybe_cleanup_point_expr_void (expr); + expr = add_stmt (expr); + return expr; +} + +/* We need to validate the arguments to __builtin_coro_promise, since the + second two must be constant, and the builtins machinery doesn't seem to + deal with that properly. */ + +tree +coro_validate_builtin_call (tree call, tsubst_flags_t) +{ + tree fn = TREE_OPERAND (CALL_EXPR_FN (call), 0); + + gcc_checking_assert (DECL_BUILT_IN_CLASS (fn) == BUILT_IN_NORMAL); + switch (DECL_FUNCTION_CODE (fn)) + { + default: + return call; + + case BUILT_IN_CORO_PROMISE: + { + /* Argument 0 is already checked by the normal built-in machinery + Argument 1 must be a constant of size type. It probably makes + little sense if it's not a power of 2, but that isn't specified + formally. */ + tree arg = CALL_EXPR_ARG (call, 1); + location_t loc = EXPR_LOCATION (arg); + + /* We expect alignof expressions in templates. */ + if (TREE_CODE (arg) == NON_DEPENDENT_EXPR + && TREE_CODE (TREE_OPERAND (arg, 0)) == ALIGNOF_EXPR) + ; + else if (!TREE_CONSTANT (arg)) + { + error_at (loc, "the align argument to %<__builtin_coro_promise%>" + " must be a constant"); + return error_mark_node; + } + /* Argument 2 is the direction - to / from handle address to promise + address. */ + arg = CALL_EXPR_ARG (call, 2); + loc = EXPR_LOCATION (arg); + if (!TREE_CONSTANT (arg)) + { + error_at (loc, "the direction argument to" + " %<__builtin_coro_promise%> must be a constant"); + return error_mark_node; + } + return call; + break; + } + } +} + +/* ================= Morph and Expand. ================= + + The entry point here is morph_fn_to_coro () which is called from + finish_function () when we have completed any template expansion. + + This is preceded by helper functions that implement the phases below. + + The process proceeds in four phases. + + A Initial framing. + The user's function body is wrapped in the initial and final suspend + points and we begin building the coroutine frame. + We build empty decls for the actor and destroyer functions at this + time too. + When exceptions are enabled, the user's function body will also be + wrapped in a try-catch block with the catch invoking the promise + class 'unhandled_exception' method. + + B Analysis. + The user's function body is analyzed to determine the suspend points, + if any, and to capture local variables that might persist across such + suspensions. In most cases, it is not necessary to capture compiler + temporaries, since the tree-lowering nests the suspensions correctly. + However, in the case of a captured reference, there is a lifetime + extension to the end of the full expression - which can mean across a + suspend point in which case it must be promoted to a frame variable. + + At the conclusion of analysis, we have a conservative frame layout and + maps of the local variables to their frame entry points. + + C Build the ramp function. + Carry out the allocation for the coroutine frame (NOTE; the actual size + computation is deferred until late in the middle end to allow for future + optimizations that will be allowed to elide unused frame entries). + We build the return object. + + D Build and expand the actor and destroyer function bodies. + The destroyer is a trivial shim that sets a bit to indicate that the + destroy dispatcher should be used and then calls into the actor. + + The actor function is the implementation of the user's state machine. + The current suspend point is noted in an index. + Each suspend point is encoded as a pair of internal functions, one in + the relevant dispatcher, and one representing the suspend point. + + During this process, the user's local variables and the proxies for the + self-handle and the promise class instance are re-written to their + coroutine frame equivalents. + + The complete bodies for the ramp, actor and destroy function are passed + back to finish_function for folding and gimplification. */ + +/* Helpers to build EXPR_STMT and void-cast EXPR_STMT, common ops. */ + +static tree +coro_build_expr_stmt (tree expr, location_t loc) +{ + return maybe_cleanup_point_expr_void (build_stmt (loc, EXPR_STMT, expr)); +} + +static tree +coro_build_cvt_void_expr_stmt (tree expr, location_t loc) +{ + tree t = build1 (CONVERT_EXPR, void_type_node, expr); + return coro_build_expr_stmt (t, loc); +} + +/* Helpers for label creation: + 1. Create a named label in the specified context. */ + +static tree +create_anon_label_with_ctx (location_t loc, tree ctx) +{ + tree lab = build_decl (loc, LABEL_DECL, NULL_TREE, void_type_node); + + DECL_CONTEXT (lab) = ctx; + DECL_ARTIFICIAL (lab) = true; + DECL_IGNORED_P (lab) = true; + TREE_USED (lab) = true; + return lab; +} + +/* 2. Create a named label in the specified context. */ + +static tree +create_named_label_with_ctx (location_t loc, const char *name, tree ctx) +{ + tree lab_id = get_identifier (name); + tree lab = define_label (loc, lab_id); + DECL_CONTEXT (lab) = ctx; + DECL_ARTIFICIAL (lab) = true; + TREE_USED (lab) = true; + return lab; +} + +struct proxy_replace +{ + tree from, to; +}; + +static tree +replace_proxy (tree *here, int *do_subtree, void *d) +{ + proxy_replace *data = (proxy_replace *) d; + + if (*here == data->from) + { + *here = data->to; + *do_subtree = 0; + } + else + *do_subtree = 1; + return NULL_TREE; +} + +/* Support for expansion of co_return statements. */ + +struct coro_ret_data +{ + tree promise_proxy; + tree real_promise; + tree fs_label; +}; + +/* If this is a coreturn statement (or one wrapped in a cleanup) then + return the list of statements to replace it. */ + +static tree +coro_maybe_expand_co_return (tree co_ret_expr, coro_ret_data *data) +{ + /* Look inside <(void) (expr)> cleanup */ + if (TREE_CODE (co_ret_expr) == CLEANUP_POINT_EXPR) + co_ret_expr = TREE_OPERAND (co_ret_expr, 0); + + if (TREE_CODE (co_ret_expr) != CO_RETURN_EXPR) + return NULL_TREE; + + location_t loc = EXPR_LOCATION (co_ret_expr); + tree expr = TREE_OPERAND (co_ret_expr, 0); + tree call = TREE_OPERAND (co_ret_expr, 1); + tree stmt_list = NULL; + if (expr && VOID_TYPE_P (TREE_TYPE (expr))) + { + /* [stmt.return.coroutine], 2.2 + If expr is present and void, it is placed immediately before + the call for return_void; */ + expr = maybe_cleanup_point_expr_void (expr); + append_to_statement_list (expr, &stmt_list); + } + + /* Now replace the promise proxy with its real value. */ + proxy_replace p_data; + p_data.from = data->promise_proxy; + p_data.to = data->real_promise; + cp_walk_tree (&call, replace_proxy, &p_data, NULL); + + /* The types of p.return_void and p.return_value are not explicitly stated + at least in n4835, it is expected that they will return void. */ + call = maybe_cleanup_point_expr_void (call); + append_to_statement_list (call, &stmt_list); + tree r = build1_loc (loc, GOTO_EXPR, void_type_node, data->fs_label); + append_to_statement_list (r, &stmt_list); + return stmt_list; +} + +/* Callback that rewrites co_return as per [stmt.return.coroutine] + - for co_return; + { p.return_void (); goto final_suspend; } + - for co_return [void expr]; + { expr; p.return_void(); goto final_suspend;} + - for co_return [non void expr]; + { p.return_value(expr); goto final_suspend; } */ + +static tree +co_return_expander (tree *stmt, int *do_subtree, void *d) +{ + coro_ret_data *data = (coro_ret_data *) d; + + /* To avoid nesting statement lists, walk them and insert as needed. */ + if (TREE_CODE (*stmt) == STATEMENT_LIST) + { + tree_stmt_iterator i; + for (i = tsi_start (*stmt); !tsi_end_p (i); tsi_next (&i)) + { + tree *new_stmt = tsi_stmt_ptr (i); + tree replace = coro_maybe_expand_co_return (*new_stmt, data); + /* If we got something, it will be list and we want to splice + it in. */ + if (replace != NULL_TREE) + { + /* Splice it in ... */ + tsi_link_before (&i, replace, TSI_SAME_STMT); + /* ... and delete what we expanded. */ + tsi_delink (&i); + /* Maybe, even likely, we replaced the last in the list. */ + if (tsi_end_p (i)) + break; + } + else /* Continue the walk. */ + cp_walk_tree (new_stmt, co_return_expander, d, NULL); + } + *do_subtree = 0; /* Done subtrees. */ + } + else + { + /* We might have a single co_return statement, in which case, we do + have to replace it with a list. */ + tree replace = coro_maybe_expand_co_return (*stmt, data); + if (replace != NULL_TREE) + { + *stmt = replace; + *do_subtree = 0; /* Done here. */ + } + } + return NULL_TREE; +} + +/* Walk the original function body, rewriting co_returns. */ + +static tree +expand_co_returns (tree *fnbody, tree promise_proxy, tree promise, + tree fs_label) +{ + coro_ret_data data = {promise_proxy, promise, fs_label}; + cp_walk_tree (fnbody, co_return_expander, &data, NULL); + return *fnbody; +} + +/* Support for expansion of co_await statements. */ + +struct coro_aw_data +{ + tree actor_fn; /* Decl for context. */ + tree coro_fp; /* Frame pointer var. */ + tree resume_idx; /* This is the index var in the frame. */ + tree self_h; /* This is a handle to the current coro (frame var). */ + tree cleanup; /* This is where to go once we complete local destroy. */ + tree cororet; /* This is where to go if we suspend. */ + unsigned index; /* This is our current resume index. */ +}; + +static tree +co_await_find_in_subtree (tree *stmt, int *do_subtree ATTRIBUTE_UNUSED, void *d) +{ + tree **p = (tree **) d; + if (TREE_CODE (*stmt) == CO_AWAIT_EXPR) + { + *p = stmt; + return *stmt; + } + return NULL_TREE; +} + +/* When we come here: + the first operand is the [currently unused] handle for suspend. + the second operand is the var to be copy-initialized + the third operand is 'o' (the initializer for the second) + as defined in [await.expr] (3.3) + the fourth operand is the mode as per the comment on build_co_await (). + + When we leave: + the IFN_CO_YIELD carries the labels of the resume and destroy + branch targets for this await. */ + +static tree +co_await_expander (tree *stmt, int * /*do_subtree*/, void *d) +{ + if (STATEMENT_CLASS_P (*stmt) || !EXPR_P (*stmt)) + return NULL_TREE; + + coro_aw_data *data = (coro_aw_data *) d; + + enum tree_code stmt_code = TREE_CODE (*stmt); + tree stripped_stmt = *stmt; + + /* Look inside <(void) (expr)> cleanup */ + if (stmt_code == CLEANUP_POINT_EXPR) + { + stripped_stmt = TREE_OPERAND (*stmt, 0); + stmt_code = TREE_CODE (stripped_stmt); + if (stmt_code == EXPR_STMT + && (TREE_CODE (EXPR_STMT_EXPR (stripped_stmt)) == CONVERT_EXPR + || TREE_CODE (EXPR_STMT_EXPR (stripped_stmt)) == CAST_EXPR) + && VOID_TYPE_P (TREE_TYPE (EXPR_STMT_EXPR (stripped_stmt)))) + { + stripped_stmt = TREE_OPERAND (EXPR_STMT_EXPR (stripped_stmt), 0); + stmt_code = TREE_CODE (stripped_stmt); + } + } + + tree *buried_stmt = NULL; + tree saved_co_await = NULL_TREE; + enum tree_code sub_code = NOP_EXPR; + + if (stmt_code == EXPR_STMT + && TREE_CODE (EXPR_STMT_EXPR (stripped_stmt)) == CO_AWAIT_EXPR) + saved_co_await + = EXPR_STMT_EXPR (stripped_stmt); /* hopefully, a void exp. */ + else if (stmt_code == MODIFY_EXPR || stmt_code == INIT_EXPR) + { + sub_code = TREE_CODE (TREE_OPERAND (stripped_stmt, 1)); + if (sub_code == CO_AWAIT_EXPR) + saved_co_await = TREE_OPERAND (stripped_stmt, 1); /* Get the RHS. */ + else if (tree r + = cp_walk_tree (&TREE_OPERAND (stripped_stmt, 1), + co_await_find_in_subtree, &buried_stmt, NULL)) + saved_co_await = r; + } + else if (stmt_code == CALL_EXPR) + { + if (tree r = cp_walk_tree (&stripped_stmt, co_await_find_in_subtree, + &buried_stmt, NULL)) + saved_co_await = r; + } + + if (!saved_co_await) + return NULL_TREE; + + /* We want to splice in the await_resume() value in some cases. */ + tree saved_statement = *stmt; + + tree actor = data->actor_fn; + location_t loc = EXPR_LOCATION (*stmt); + tree sv_handle = TREE_OPERAND (saved_co_await, 0); + tree var = TREE_OPERAND (saved_co_await, 1); /* frame slot. */ + tree expr = TREE_OPERAND (saved_co_await, 2); /* initializer. */ + tree awaiter_calls = TREE_OPERAND (saved_co_await, 3); + + tree source = TREE_OPERAND (saved_co_await, 4); + bool is_final = (source + && TREE_INT_CST_LOW (source) == (int) FINAL_SUSPEND_POINT); + bool needs_dtor = TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (var)); + int resume_point = data->index; + size_t bufsize = sizeof ("destroy.") + 10; + char *buf = (char *) alloca (bufsize); + snprintf (buf, bufsize, "destroy.%d", resume_point); + tree destroy_label = create_named_label_with_ctx (loc, buf, actor); + snprintf (buf, bufsize, "resume.%d", resume_point); + tree resume_label = create_named_label_with_ctx (loc, buf, actor); + tree empty_list = build_empty_stmt (loc); + + tree dtor = NULL_TREE; + tree await_type = TREE_TYPE (var); + if (needs_dtor) + dtor = build_special_member_call (var, complete_dtor_identifier, NULL, + await_type, LOOKUP_NORMAL, + tf_warning_or_error); + + tree stmt_list = NULL; + /* Initialize the var from the provided 'o' expression. */ + tree r = build2 (INIT_EXPR, await_type, var, expr); + r = coro_build_cvt_void_expr_stmt (r, loc); + append_to_statement_list (r, &stmt_list); + + /* Use the await_ready() call to test if we need to suspend. */ + tree ready_cond = TREE_VEC_ELT (awaiter_calls, 0); /* await_ready(). */ + ready_cond = build1_loc (loc, TRUTH_NOT_EXPR, boolean_type_node, ready_cond); + ready_cond + = build1_loc (loc, CLEANUP_POINT_EXPR, boolean_type_node, ready_cond); + + tree body_list = NULL; + tree susp_idx = build_int_cst (short_unsigned_type_node, data->index); + r = build2_loc (loc, MODIFY_EXPR, short_unsigned_type_node, data->resume_idx, + susp_idx); + r = coro_build_cvt_void_expr_stmt (r, loc); + append_to_statement_list (r, &body_list); + + tree suspend = TREE_VEC_ELT (awaiter_calls, 1); /* await_suspend(). */ + + if (sv_handle == NULL_TREE) + { + /* void return, we just call it and hit the yield. */ + suspend = coro_build_cvt_void_expr_stmt (suspend, loc); + append_to_statement_list (suspend, &body_list); + } + else if (sv_handle == boolean_type_node) + { + /* Boolean return, continue if the call returns false. */ + suspend = build1_loc (loc, TRUTH_NOT_EXPR, boolean_type_node, suspend); + suspend + = build1_loc (loc, CLEANUP_POINT_EXPR, boolean_type_node, suspend); + tree go_on = build1_loc (loc, GOTO_EXPR, void_type_node, resume_label); + r = build3_loc (loc, COND_EXPR, void_type_node, suspend, go_on, + empty_list); + append_to_statement_list (r, &body_list); + } + else + { + r = build2_loc (loc, INIT_EXPR, TREE_TYPE (sv_handle), sv_handle, + suspend); + append_to_statement_list (r, &body_list); + tree resume + = lookup_member (TREE_TYPE (sv_handle), coro_resume_identifier, 1, 0, + tf_warning_or_error); + resume = build_new_method_call (sv_handle, resume, NULL, NULL_TREE, + LOOKUP_NORMAL, NULL, tf_warning_or_error); + resume = coro_build_cvt_void_expr_stmt (resume, loc); + append_to_statement_list (resume, &body_list); + } + + tree d_l + = build1 (ADDR_EXPR, build_reference_type (void_type_node), destroy_label); + tree r_l + = build1 (ADDR_EXPR, build_reference_type (void_type_node), resume_label); + tree susp + = build1 (ADDR_EXPR, build_reference_type (void_type_node), data->cororet); + tree final_susp = build_int_cst (integer_type_node, is_final ? 1 : 0); + + susp_idx = build_int_cst (integer_type_node, data->index); + + tree sw = begin_switch_stmt (); + tree cond = build_decl (loc, VAR_DECL, NULL_TREE, integer_type_node); + DECL_ARTIFICIAL (cond) = 1; + DECL_IGNORED_P (cond) = 1; + layout_decl (cond, 0); + + r = build_call_expr_internal_loc (loc, IFN_CO_YIELD, integer_type_node, 5, + susp_idx, final_susp, r_l, d_l, + data->coro_fp); + r = build2 (INIT_EXPR, integer_type_node, cond, r); + finish_switch_cond (r, sw); + r = build_case_label (build_int_cst (integer_type_node, 0), NULL_TREE, + create_anon_label_with_ctx (loc, actor)); + add_stmt (r); /* case 0: */ + /* Implement the suspend, a scope exit without clean ups. */ + r = build_call_expr_internal_loc (loc, IFN_CO_SUSPN, void_type_node, 1, susp); + r = coro_build_cvt_void_expr_stmt (r, loc); + add_stmt (r); // goto ret; + r = build_case_label (build_int_cst (integer_type_node, 1), NULL_TREE, + create_anon_label_with_ctx (loc, actor)); + add_stmt (r); // case 1: + r = build1_loc (loc, GOTO_EXPR, void_type_node, resume_label); + add_stmt (r); // goto resume; + r = build_case_label (NULL_TREE, NULL_TREE, + create_anon_label_with_ctx (loc, actor)); + add_stmt (r); // default:; + r = build1_loc (loc, GOTO_EXPR, void_type_node, destroy_label); + add_stmt (r); // goto destroy; + + /* part of finish switch. */ + SWITCH_STMT_BODY (sw) = pop_stmt_list (SWITCH_STMT_BODY (sw)); + pop_switch (); + tree scope = SWITCH_STMT_SCOPE (sw); + SWITCH_STMT_SCOPE (sw) = NULL; + r = do_poplevel (scope); + append_to_statement_list (r, &body_list); + + destroy_label = build_stmt (loc, LABEL_EXPR, destroy_label); + append_to_statement_list (destroy_label, &body_list); + if (needs_dtor) + append_to_statement_list (dtor, &body_list); + r = build1_loc (loc, GOTO_EXPR, void_type_node, data->cleanup); + append_to_statement_list (r, &body_list); + + r = build3_loc (loc, COND_EXPR, void_type_node, ready_cond, body_list, + empty_list); + + append_to_statement_list (r, &stmt_list); + + /* Resume point. */ + resume_label = build_stmt (loc, LABEL_EXPR, resume_label); + append_to_statement_list (resume_label, &stmt_list); + + /* This will produce the value (if one is provided) from the co_await + expression. */ + tree resume_call = TREE_VEC_ELT (awaiter_calls, 2); /* await_resume(). */ + switch (stmt_code) + { + default: /* not likely to work .. but... */ + append_to_statement_list (resume_call, &stmt_list); + break; + case INIT_EXPR: + case MODIFY_EXPR: + case CALL_EXPR: + /* Replace the use of co_await by the resume expr. */ + if (sub_code == CO_AWAIT_EXPR) + { + /* We're updating the interior of a possibly <(void) expr>cleanup. */ + TREE_OPERAND (stripped_stmt, 1) = resume_call; + append_to_statement_list (saved_statement, &stmt_list); + } + else if (buried_stmt != NULL) + { + *buried_stmt = resume_call; + append_to_statement_list (saved_statement, &stmt_list); + } + else + { + error_at (loc, "failed to substitute the resume method in %qE", + saved_statement); + append_to_statement_list (saved_statement, &stmt_list); + } + break; + } + if (needs_dtor) + append_to_statement_list (dtor, &stmt_list); + data->index += 2; + *stmt = stmt_list; + return NULL_TREE; +} + +static tree +expand_co_awaits (tree fn, tree *fnbody, tree coro_fp, tree resume_idx, + tree cleanup, tree cororet, tree self_h) +{ + coro_aw_data data + = {fn, coro_fp, resume_idx, self_h, cleanup, cororet, 2}; + cp_walk_tree (fnbody, co_await_expander, &data, NULL); + return *fnbody; +} + +/* Suspend point hash_map. */ + +struct suspend_point_info +{ + /* coro frame field type. */ + tree awaitable_type; + /* coro frame field name. */ + tree await_field_id; + /* suspend method return type. */ + tree suspend_type; + /* suspend handle field name, NULL_TREE if not needed. */ + tree susp_handle_id; +}; + +static hash_map *suspend_points; + +struct await_xform_data +{ + tree actor_frame; + tree promise_proxy; + tree real_promise; + tree self_h_proxy; + tree real_self_h; +}; + +/* When we built the await expressions, we didn't know the coro frame + layout, therefore no idea where to find the promise or where to put + the awaitables. Now we know these things, fill them in. */ + +static tree +transform_await_expr (tree await_expr, await_xform_data *xform) +{ + suspend_point_info *si = suspend_points->get (await_expr); + location_t loc = EXPR_LOCATION (await_expr); + if (!si) + { + error_at (loc, "no suspend point info for %qD", await_expr); + return error_mark_node; + } + + /* So, on entry, we have: + in : CO_AWAIT_EXPR (a, e_proxy, o, awr_call_vector, mode) + We no longer need a [it had diagnostic value, maybe?] + We need to replace the promise proxy in all elements + We need to replace the e_proxy in the awr_call. + */ + + tree coro_frame_type = TREE_TYPE (xform->actor_frame); + tree ah = NULL_TREE; + if (si->susp_handle_id) + { + tree ah_m + = lookup_member (coro_frame_type, si->susp_handle_id, + /*protect*/ 1, /*want_type*/ 0, tf_warning_or_error); + ah = build_class_member_access_expr (xform->actor_frame, ah_m, NULL_TREE, + true, tf_warning_or_error); + } + else if (TREE_CODE (si->suspend_type) == BOOLEAN_TYPE) + ah = boolean_type_node; + + /* Replace Op 0 with the frame slot for the temporary handle, if it's needed. + If there's no frame type to be stored we flag boolean_type for that case + and an empty pointer for void return. */ + TREE_OPERAND (await_expr, 0) = ah; + + /* Get a reference to the initial suspend var in the frame. */ + tree as_m + = lookup_member (coro_frame_type, si->await_field_id, + /*protect*/ 1, /*want_type*/ 0, tf_warning_or_error); + tree as = build_class_member_access_expr (xform->actor_frame, as_m, NULL_TREE, + true, tf_warning_or_error); + + /* Replace references to the instance proxy with the frame entry now + computed. */ + proxy_replace data = {TREE_OPERAND (await_expr, 1), as}; + cp_walk_tree (&await_expr, replace_proxy, &data, NULL); + + /* .. and replace. */ + TREE_OPERAND (await_expr, 1) = as; + + /* Now do the self_handle. */ + data.from = xform->self_h_proxy; + data.to = xform->real_self_h; + cp_walk_tree (&await_expr, replace_proxy, &data, NULL); + + /* Now do the promise. */ + data.from = xform->promise_proxy; + data.to = xform->real_promise; + cp_walk_tree (&await_expr, replace_proxy, &data, NULL); + + return await_expr; +} + +/* A wrapper for the transform_await_expr function so that it can be a + callback from cp_walk_tree. */ + +static tree +transform_await_wrapper (tree *stmt, int *do_subtree, void *d) +{ + if (TREE_CODE (*stmt) != CO_AWAIT_EXPR && TREE_CODE (*stmt) != CO_YIELD_EXPR) + return NULL_TREE; + + tree await_expr = *stmt; + await_xform_data *xform = (await_xform_data *) d; + + *stmt = transform_await_expr (await_expr, xform); + if (*stmt == error_mark_node) + *do_subtree = 0; + return NULL_TREE; +} + +struct param_info +{ + tree field_id; + vec *body_uses; + tree frame_type; +}; + +struct local_var_info +{ + tree field_id; + tree field_idx; + tree frame_type; + tree captured; + location_t def_loc; +}; + +/* For figuring out what local variable usage we have. */ +struct local_vars_transform +{ + tree context; + tree actor_frame; + tree coro_frame_type; + location_t loc; + hash_map *local_var_uses; +}; + +static tree +transform_local_var_uses (tree *stmt, int *do_subtree, void *d) +{ + local_vars_transform *lvd = (local_vars_transform *) d; + + /* For each var in this bind expr (that has a frame id, which means it was + accessed), build a frame reference for each and then walk the bind expr + statements, substituting the frame ref for the original var. */ + + if (TREE_CODE (*stmt) == BIND_EXPR) + { + tree lvar; + for (lvar = BIND_EXPR_VARS (*stmt); lvar != NULL; + lvar = DECL_CHAIN (lvar)) + { + bool existed; + local_var_info &local_var + = lvd->local_var_uses->get_or_insert (lvar, &existed); + gcc_checking_assert (existed); + + /* Re-write the variable's context to be in the actor func. */ + DECL_CONTEXT (lvar) = lvd->context; + + /* we need to walk some of the decl trees, which might contain + references to vars replaced at a higher level. */ + cp_walk_tree (&DECL_INITIAL (lvar), transform_local_var_uses, d, + NULL); + cp_walk_tree (&DECL_SIZE (lvar), transform_local_var_uses, d, NULL); + cp_walk_tree (&DECL_SIZE_UNIT (lvar), transform_local_var_uses, d, + NULL); + + /* TODO: implement selective generation of fields when vars are + known not-used. */ + if (local_var.field_id == NULL_TREE) + continue; /* Wasn't used. */ + + tree fld_ref + = lookup_member (lvd->coro_frame_type, local_var.field_id, + /*protect*/ 1, /*want_type*/ 0, + tf_warning_or_error); + tree fld_idx = build3_loc (lvd->loc, COMPONENT_REF, TREE_TYPE (lvar), + lvd->actor_frame, fld_ref, NULL_TREE); + local_var.field_idx = fld_idx; + } + cp_walk_tree (&BIND_EXPR_BODY (*stmt), transform_local_var_uses, d, NULL); + /* Now we have processed and removed references to the original vars, + we can drop those from the bind. */ + for (tree *pvar = &BIND_EXPR_VARS (*stmt); *pvar != NULL;) + { + bool existed; + local_var_info &local_var + = lvd->local_var_uses->get_or_insert (*pvar, &existed); + gcc_checking_assert (existed); + + if (local_var.field_id == NULL_TREE) + pvar = &DECL_CHAIN (*pvar); /* Wasn't used. */ + + *pvar = DECL_CHAIN (*pvar); // discard this one, we replaced it. + } + + *do_subtree = 0; /* We've done the body already. */ + return NULL_TREE; + } + + tree var_decl = *stmt; + /* Look inside cleanups, we don't want to wrap a statement list in a + cleanup. */ + bool needs_cleanup = true; + if (TREE_CODE (var_decl) == CLEANUP_POINT_EXPR) + var_decl = TREE_OPERAND (var_decl, 0); + else + needs_cleanup = false; + + /* Look inside the decl_expr for the actual var. */ + bool decl_expr_p = TREE_CODE (var_decl) == DECL_EXPR; + if (decl_expr_p && TREE_CODE (DECL_EXPR_DECL (var_decl)) == VAR_DECL) + var_decl = DECL_EXPR_DECL (var_decl); + else if (TREE_CODE (var_decl) != VAR_DECL) + return NULL_TREE; + + /* VAR_DECLs that are not recorded can belong to the proxies we've placed + for the promise and coroutine handle(s), to global vars or to compiler + temporaries. Skip past these, we will handle them later. */ + local_var_info *local_var_i = lvd->local_var_uses->get (var_decl); + if (local_var_i == NULL) + return NULL_TREE; + + /* This is our revised 'local' i.e. a frame slot. */ + tree revised = local_var_i->field_idx; + gcc_checking_assert (DECL_CONTEXT (var_decl) == lvd->context); + + if (decl_expr_p && DECL_INITIAL (var_decl)) + { + location_t loc = DECL_SOURCE_LOCATION (var_decl); + tree r + = cp_build_modify_expr (loc, revised, INIT_EXPR, + DECL_INITIAL (var_decl), tf_warning_or_error); + if (needs_cleanup) + r = coro_build_cvt_void_expr_stmt (r, EXPR_LOCATION (*stmt)); + *stmt = r; + } + else + *stmt = revised; + + if (decl_expr_p) + *do_subtree = 0; /* We've accounted for the nested use. */ + return NULL_TREE; +} + +/* The actor transform. */ + +static void +build_actor_fn (location_t loc, tree coro_frame_type, tree actor, tree fnbody, + tree orig, hash_map *param_uses, + hash_map *local_var_uses, + vec *param_dtor_list, tree initial_await, + tree final_await, unsigned body_count) +{ + verify_stmt_tree (fnbody); + /* Some things we inherit from the original function. */ + tree coro_frame_ptr = build_pointer_type (coro_frame_type); + tree handle_type = get_coroutine_handle_type (orig); + tree self_h_proxy = get_coroutine_self_handle_proxy (orig); + tree promise_type = get_coroutine_promise_type (orig); + tree promise_proxy = get_coroutine_promise_proxy (orig); + 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); + + /* One param, the coro frame pointer. */ + tree actor_fp + = build_lang_decl (PARM_DECL, get_identifier ("frame_ptr"), coro_frame_ptr); + DECL_CONTEXT (actor_fp) = actor; + DECL_ARG_TYPE (actor_fp) = type_passed_as (coro_frame_ptr); + DECL_ARGUMENTS (actor) = actor_fp; + + /* A void return. */ + tree resdecl = build_decl (loc, RESULT_DECL, 0, void_type_node); + DECL_ARTIFICIAL (resdecl) = 1; + DECL_IGNORED_P (resdecl) = 1; + DECL_RESULT (actor) = resdecl; + DECL_COROUTINE_P (actor) = 1; + + /* We have a definition here. */ + TREE_STATIC (actor) = 1; + + tree actor_outer = push_stmt_list (); + current_stmt_tree ()->stmts_are_full_exprs_p = 1; + tree stmt = begin_compound_stmt (BCS_FN_BODY); + + /* ??? Can we dispense with the enclosing bind if the function body does + not start with a bind_expr? (i.e. there's no contained scopes). */ + tree actor_bind = build3 (BIND_EXPR, void_type_node, NULL, NULL, NULL); + tree top_block = make_node (BLOCK); + BIND_EXPR_BLOCK (actor_bind) = top_block; + + /* Update the block associated with the outer scope of the orig fn. */ + tree first = expr_first (fnbody); + if (first && TREE_CODE (first) == BIND_EXPR) + { + /* We will discard this, since it's connected to the original scope + nest. */ + tree block = BIND_EXPR_BLOCK (first); + if (block) // For this to be missing is probably a bug. + { + gcc_assert (BLOCK_SUPERCONTEXT (block) == NULL_TREE); + gcc_assert (BLOCK_CHAIN (block) == NULL_TREE); + BLOCK_SUPERCONTEXT (block) = top_block; + BLOCK_SUBBLOCKS (top_block) = block; + } + } + + add_stmt (actor_bind); + tree actor_body = push_stmt_list (); + + /* The entry point for the actor code from the ramp. */ + tree actor_begin_label + = create_named_label_with_ctx (loc, "actor.begin", actor); + tree actor_frame = build1_loc (loc, INDIRECT_REF, coro_frame_type, actor_fp); + + /* Re-write param references in the body, no code should be generated + here. */ + if (DECL_ARGUMENTS (orig) && param_uses != NULL) + { + tree arg; + for (arg = DECL_ARGUMENTS (orig); arg != NULL; arg = DECL_CHAIN (arg)) + { + bool existed; + param_info &parm = param_uses->get_or_insert (arg, &existed); + if (parm.field_id == NULL_TREE) + continue; /* Wasn't used. */ + tree fld_ref = lookup_member (coro_frame_type, parm.field_id, + /*protect*/ 1, /*want_type*/ 0, + tf_warning_or_error); + tree fld_idx = build3_loc (loc, COMPONENT_REF, TREE_TYPE (arg), + actor_frame, fld_ref, NULL_TREE); + int i; + tree *puse; + FOR_EACH_VEC_ELT (*parm.body_uses, i, puse) + { + *puse = fld_idx; + } + } + } + + /* Re-write local vars, similarly. */ + local_vars_transform xform_vars_data + = {actor, actor_frame, coro_frame_type, loc, local_var_uses}; + cp_walk_tree (&fnbody, transform_local_var_uses, &xform_vars_data, NULL); + + tree resume_idx_name = get_identifier ("__resume_at"); + tree rat_field = lookup_member (coro_frame_type, resume_idx_name, 1, 0, + tf_warning_or_error); + tree rat = build3 (COMPONENT_REF, short_unsigned_type_node, actor_frame, + rat_field, NULL_TREE); + + tree ret_label + = create_named_label_with_ctx (loc, "actor.suspend.ret", actor); + + tree lsb_if = begin_if_stmt (); + tree chkb0 = build2 (BIT_AND_EXPR, short_unsigned_type_node, rat, + build_int_cst (short_unsigned_type_node, 1)); + chkb0 = build2 (NE_EXPR, short_unsigned_type_node, chkb0, + build_int_cst (short_unsigned_type_node, 0)); + finish_if_stmt_cond (chkb0, lsb_if); + + tree destroy_dispatcher = begin_switch_stmt (); + finish_switch_cond (rat, destroy_dispatcher); + tree ddeflab = build_case_label (NULL_TREE, NULL_TREE, + create_anon_label_with_ctx (loc, actor)); + add_stmt (ddeflab); + tree b = build_call_expr_loc (loc, builtin_decl_explicit (BUILT_IN_TRAP), 0); + b = coro_build_cvt_void_expr_stmt (b, loc); + add_stmt (b); + + short unsigned lab_num = 3; + for (unsigned destr_pt = 0; destr_pt < body_count + 2; destr_pt++) + { + tree l_num = build_int_cst (short_unsigned_type_node, lab_num); + b = build_case_label (l_num, NULL_TREE, + create_anon_label_with_ctx (loc, actor)); + add_stmt (b); + b = build_call_expr_internal_loc (loc, IFN_CO_ACTOR, void_type_node, 1, + l_num); + b = coro_build_cvt_void_expr_stmt (b, loc); + add_stmt (b); + b = build1 (GOTO_EXPR, void_type_node, CASE_LABEL (ddeflab)); + add_stmt (b); + lab_num += 2; + } + + /* Insert the prototype dispatcher. */ + finish_switch_stmt (destroy_dispatcher); + + finish_then_clause (lsb_if); + + tree dispatcher = begin_switch_stmt (); + finish_switch_cond (rat, dispatcher); + b = build_case_label (build_int_cst (short_unsigned_type_node, 0), NULL_TREE, + create_anon_label_with_ctx (loc, actor)); + add_stmt (b); + b = build1 (GOTO_EXPR, void_type_node, actor_begin_label); + add_stmt (b); + + tree rdeflab = build_case_label (NULL_TREE, NULL_TREE, + create_anon_label_with_ctx (loc, actor)); + add_stmt (rdeflab); + b = build_call_expr_loc (loc, builtin_decl_explicit (BUILT_IN_TRAP), 0); + b = coro_build_cvt_void_expr_stmt (b, loc); + add_stmt (b); + + lab_num = 2; + /* The final resume should be made to hit the default (trap, UB) entry. */ + for (unsigned resu_pt = 0; resu_pt < body_count + 1; resu_pt++) + { + tree l_num = build_int_cst (short_unsigned_type_node, lab_num); + b = build_case_label (l_num, NULL_TREE, + create_anon_label_with_ctx (loc, actor)); + add_stmt (b); + b = build_call_expr_internal_loc (loc, IFN_CO_ACTOR, void_type_node, 1, + l_num); + b = coro_build_cvt_void_expr_stmt (b, loc); + add_stmt (b); + b = build1 (GOTO_EXPR, void_type_node, CASE_LABEL (rdeflab)); + add_stmt (b); + lab_num += 2; + } + + /* Insert the prototype dispatcher. */ + finish_switch_stmt (dispatcher); + + finish_if_stmt (lsb_if); + + tree r = build_stmt (loc, LABEL_EXPR, actor_begin_label); + add_stmt (r); + + /* actor's version of the promise. */ + tree ap_m = lookup_member (coro_frame_type, get_identifier ("__p"), 1, 0, + tf_warning_or_error); + tree ap = build_class_member_access_expr (actor_frame, ap_m, NULL_TREE, false, + tf_warning_or_error); + + /* actor's coroutine 'self handle'. */ + tree ash_m = lookup_member (coro_frame_type, get_identifier ("__self_h"), 1, + 0, tf_warning_or_error); + tree ash = build_class_member_access_expr (actor_frame, ash_m, NULL_TREE, + false, tf_warning_or_error); + /* So construct the self-handle from the frame address. */ + tree hfa_m = lookup_member (handle_type, coro_from_address_identifier, 1, + 0, tf_warning_or_error); + + r = build1 (CONVERT_EXPR, build_pointer_type (void_type_node), actor_fp); + vec *args = make_tree_vector_single (r); + tree hfa = build_new_method_call (ash, hfa_m, &args, NULL_TREE, LOOKUP_NORMAL, + NULL, tf_warning_or_error); + r = build2 (INIT_EXPR, handle_type, ash, hfa); + r = coro_build_cvt_void_expr_stmt (r, loc); + add_stmt (r); + release_tree_vector (args); + + /* Now we know the real promise, and enough about the frame layout to + decide where to put things. */ + + await_xform_data xform + = {actor_frame, promise_proxy, ap, self_h_proxy, ash}; + + /* Get a reference to the initial suspend var in the frame. */ + transform_await_expr (initial_await, &xform); + r = coro_build_expr_stmt (initial_await, loc); + add_stmt (r); + + /* Now we've built the promise etc, process fnbody for co_returns. + We want the call to return_void () below and it has no params so + we can create it once here. + Calls to return_value () will have to be checked and created as + required. */ + + tree return_void = NULL_TREE; + tree rvm + = lookup_promise_method (orig, coro_return_void_identifier, loc, + /*musthave=*/ false); + if (rvm && rvm != error_mark_node) + return_void + = build_new_method_call (ap, rvm, NULL, NULL_TREE, LOOKUP_NORMAL, NULL, + tf_warning_or_error); + + /* co_return branches to the final_suspend label, so declare that now. */ + tree fs_label = create_named_label_with_ctx (loc, "final.suspend", actor); + + /* Expand co_returns in the saved function body */ + fnbody = expand_co_returns (&fnbody, promise_proxy, ap, fs_label); + + /* Transform the await expressions in the function body. Only do each + await tree once! */ + hash_set pset; + cp_walk_tree (&fnbody, transform_await_wrapper, &xform, &pset); + + /* Add in our function body with the co_returns rewritten to final form. */ + add_stmt (fnbody); + + /* [stmt.return.coroutine] (2.2 : 3) if p.return_void() is a valid + expression, flowing off the end of a coroutine is equivalent to + co_return; otherwise UB. + We just inject the call to p.return_void() here, and fall through to + the final_suspend: label (eliding the goto). If the function body has + a co_return, then this statement will be unreachable and DCEd. */ + if (return_void != NULL_TREE) + add_stmt (return_void); + + /* Final suspend starts here. */ + r = build_stmt (loc, LABEL_EXPR, fs_label); + add_stmt (r); + + /* Set the actor pointer to null, so that 'done' will work. + Resume from here is UB anyway - although a 'ready' await will + branch to the final resume, and fall through to the destroy. */ + tree resume_m + = lookup_member (coro_frame_type, get_identifier ("__resume"), + /*protect*/ 1, /*want_type*/ 0, tf_warning_or_error); + tree res_x = build_class_member_access_expr (actor_frame, resume_m, NULL_TREE, + false, tf_warning_or_error); + r = build1 (CONVERT_EXPR, act_des_fn_ptr, integer_zero_node); + r = build2 (INIT_EXPR, act_des_fn_ptr, res_x, r); + r = coro_build_cvt_void_expr_stmt (r, loc); + add_stmt (r); + + /* Get a reference to the final suspend var in the frame. */ + transform_await_expr (final_await, &xform); + r = coro_build_expr_stmt (final_await, loc); + add_stmt (r); + + /* now do the tail of the function. */ + tree del_promise_label + = create_named_label_with_ctx (loc, "coro.delete.promise", actor); + r = build_stmt (loc, LABEL_EXPR, del_promise_label); + add_stmt (r); + + /* Destructors for the things we built explicitly. */ + r = build_special_member_call (ap, complete_dtor_identifier, NULL, + promise_type, LOOKUP_NORMAL, + tf_warning_or_error); + add_stmt (r); + + tree del_frame_label + = create_named_label_with_ctx (loc, "coro.delete.frame", actor); + r = build_stmt (loc, LABEL_EXPR, del_frame_label); + add_stmt (r); + + /* Here deallocate the frame (if we allocated it), which we will have at + present. */ + tree fnf_m + = lookup_member (coro_frame_type, get_identifier ("__frame_needs_free"), 1, + 0, tf_warning_or_error); + tree fnf2_x = build_class_member_access_expr (actor_frame, fnf_m, NULL_TREE, + false, tf_warning_or_error); + + tree need_free_if = begin_if_stmt (); + 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) + { + 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); + tree t = TREE_TYPE (a); + tree dtor; + dtor + = build_special_member_call (a, complete_dtor_identifier, NULL, t, + LOOKUP_NORMAL, tf_warning_or_error); + add_stmt (dtor); + } + } + + tree delname = ovl_op_identifier (false, DELETE_EXPR); + tree arg = build1 (CONVERT_EXPR, ptr_type_node, actor_fp); + vec *arglist = make_tree_vector_single (arg); + + /* The user can (optionally) provide a delete function in the promise + type, it's not a failure for it to be absent. */ + tree fns = lookup_promise_method (orig, delname, loc, false); + tree del_coro_fr = NULL_TREE; + if (fns && fns != error_mark_node) + { + del_coro_fr = lookup_arg_dependent (delname, fns, arglist); + if (OVL_P (del_coro_fr)) + del_coro_fr = OVL_FIRST (del_coro_fr); + else + del_coro_fr = BASELINK_FUNCTIONS (del_coro_fr); + + gcc_checking_assert (DECL_STATIC_FUNCTION_P (del_coro_fr)); + TREE_USED (del_coro_fr) = 1; + del_coro_fr = build_call_expr_loc_vec (loc, del_coro_fr, arglist); + } + + /* If that fails, then fall back to the global delete operator. */ + if (del_coro_fr == NULL_TREE || del_coro_fr == error_mark_node) + { + fns =lookup_name_real (delname, 0, 1, /*block_p=*/true, 0, 0); + del_coro_fr = lookup_arg_dependent (del_coro_fr, fns, arglist); + del_coro_fr = build_new_function_call (del_coro_fr, &arglist, true); + } + + del_coro_fr = coro_build_cvt_void_expr_stmt (del_coro_fr, loc); + add_stmt (del_coro_fr); + finish_then_clause (need_free_if); + tree scope = IF_SCOPE (need_free_if); + IF_SCOPE (need_free_if) = NULL; + r = do_poplevel (scope); + add_stmt (r); + + /* done. */ + r = build_stmt (loc, RETURN_EXPR, NULL); + TREE_NO_WARNING (r) |= 1; /* We don't want a warning about this. */ + r = maybe_cleanup_point_expr_void (r); + add_stmt (r); + + /* This is the suspend return point. */ + r = build_stmt (loc, LABEL_EXPR, ret_label); + add_stmt (r); + + r = build_stmt (loc, RETURN_EXPR, NULL); + TREE_NO_WARNING (r) |= 1; /* We don't want a warning about this. */ + r = maybe_cleanup_point_expr_void (r); + add_stmt (r); + + /* We need the resume index to work with. */ + tree res_idx_m + = lookup_member (coro_frame_type, resume_idx_name, + /*protect*/ 1, /*want_type*/ 0, tf_warning_or_error); + tree res_idx + = build_class_member_access_expr (actor_frame, res_idx_m, NULL_TREE, false, + tf_warning_or_error); + + /* We've now rewritten the tree and added the initial and final + co_awaits. Now pass over the tree and expand the co_awaits. */ + actor_body = expand_co_awaits (actor, &actor_body, actor_fp, res_idx, + del_promise_label, ret_label, ash); + + actor_body = pop_stmt_list (actor_body); + BIND_EXPR_BODY (actor_bind) = actor_body; + + finish_compound_stmt (stmt); + DECL_SAVED_TREE (actor) = pop_stmt_list (actor_outer); + verify_stmt_tree (DECL_SAVED_TREE (actor)); +} + +/* The prototype 'destroy' function : + frame->__resume_at |= 1; + actor (frame); */ + +static void +build_destroy_fn (location_t loc, tree coro_frame_type, tree destroy, + tree actor) +{ + /* One param, the coro frame pointer. */ + tree coro_frame_ptr = build_pointer_type (coro_frame_type); + tree destr_fp + = build_lang_decl (PARM_DECL, get_identifier ("frame_ptr"), coro_frame_ptr); + DECL_CONTEXT (destr_fp) = destroy; + DECL_ARG_TYPE (destr_fp) = type_passed_as (coro_frame_ptr); + DECL_ARGUMENTS (destroy) = destr_fp; + + /* A void return. */ + tree resdecl = build_decl (loc, RESULT_DECL, 0, void_type_node); + DECL_ARTIFICIAL (resdecl) = 1; + DECL_IGNORED_P (resdecl) = 1; + DECL_RESULT (destroy) = resdecl; + + /* We have a definition here. */ + TREE_STATIC (destroy) = 1; + DECL_COROUTINE_P (destroy) = 1; + + tree destr_outer = push_stmt_list (); + current_stmt_tree ()->stmts_are_full_exprs_p = 1; + tree dstr_stmt = begin_compound_stmt (BCS_FN_BODY); + + tree destr_frame = build1 (INDIRECT_REF, coro_frame_type, destr_fp); + + tree resume_idx_name = get_identifier ("__resume_at"); + tree rat_field = lookup_member (coro_frame_type, resume_idx_name, 1, 0, + tf_warning_or_error); + tree rat = build3 (COMPONENT_REF, short_unsigned_type_node, destr_frame, + rat_field, NULL_TREE); + + /* _resume_at |= 1 */ + tree dstr_idx = build2 (BIT_IOR_EXPR, short_unsigned_type_node, rat, + build_int_cst (short_unsigned_type_node, 1)); + tree r = build2 (MODIFY_EXPR, short_unsigned_type_node, rat, dstr_idx); + r = coro_build_cvt_void_expr_stmt (r, loc); + add_stmt (r); + + /* So .. call the actor .. */ + r = build_call_expr_loc (loc, actor, 1, destr_fp); + r = coro_build_cvt_void_expr_stmt (r, loc); + add_stmt (r); + + /* done. */ + r = build_stmt (loc, RETURN_EXPR, NULL); + r = maybe_cleanup_point_expr_void (r); + add_stmt (r); + + finish_compound_stmt (dstr_stmt); + DECL_SAVED_TREE (destroy) = pop_stmt_list (destr_outer); +} + +/* Helper that returns an identifier for an appended extension to the + current un-mangled function name. */ + +static tree +get_fn_local_identifier (tree orig, const char *append) +{ + /* Figure out the bits we need to generate names for the outlined things + For consistency, this needs to behave the same way as + ASM_FORMAT_PRIVATE_NAME does. */ + tree nm = DECL_NAME (orig); + const char *sep, *pfx = ""; +#ifndef NO_DOT_IN_LABEL + sep = "."; +#else +#ifndef NO_DOLLAR_IN_LABEL + sep = "$" +#else + sep = "_"; + pfx = "__"; +#endif +#endif + + char *an; + if (DECL_ASSEMBLER_NAME (orig)) + an = ACONCAT ((IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (orig)), sep, append, + (char *) 0)); + else if (DECL_USE_TEMPLATE (orig) && DECL_TEMPLATE_INFO (orig) + && DECL_TI_ARGS (orig)) + { + tree tpl_args = DECL_TI_ARGS (orig); + an = ACONCAT ((pfx, IDENTIFIER_POINTER (nm), (char *) 0)); + for (int i = 0; i < TREE_VEC_LENGTH (tpl_args); ++i) + { + tree typ = DECL_NAME (TYPE_NAME (TREE_VEC_ELT (tpl_args, i))); + an = ACONCAT ((an, sep, IDENTIFIER_POINTER (typ), (char *) 0)); + } + an = ACONCAT ((an, sep, append, (char *) 0)); + } + else + an = ACONCAT ((pfx, IDENTIFIER_POINTER (nm), sep, append, (char *) 0)); + + return get_identifier (an); +} + +static tree +build_init_or_final_await (location_t loc, bool is_final) +{ + tree suspend_alt = is_final ? coro_final_suspend_identifier + : coro_initial_suspend_identifier; + tree setup_meth = lookup_promise_method (current_function_decl, suspend_alt, + loc, /*musthave=*/ true); + if (!setup_meth || setup_meth == error_mark_node) + return error_mark_node; + + tree s_fn = NULL_TREE; + tree setup_call = build_new_method_call ( + get_coroutine_promise_proxy (current_function_decl), setup_meth, NULL, + NULL_TREE, LOOKUP_NORMAL, &s_fn, tf_warning_or_error); + + if (!s_fn || setup_call == error_mark_node) + return error_mark_node; + + /* So build the co_await for this */ + /* For initial/final suspends the call is is "a" per [expr.await] 3.2. */ + return build_co_await (loc, setup_call, (is_final ? FINAL_SUSPEND_POINT + : INITIAL_SUSPEND_POINT)); +} + +/* Callback to record the essential data for each await point found in the + function. */ + +static bool +register_await_info (tree await_expr, tree aw_type, tree aw_nam, tree susp_type, + tree susp_handle_nam) +{ + bool seen; + suspend_point_info &s + = suspend_points->get_or_insert (await_expr, &seen); + if (seen) + { + error_at (EXPR_LOCATION (await_expr), "duplicate info for %qE", + await_expr); + return false; + } + s.awaitable_type = aw_type; + s.await_field_id = aw_nam; + s.suspend_type = susp_type; + s.susp_handle_id = susp_handle_nam; + return true; +} + +/* Small helper for the repetitive task of adding a new field to the coro + frame type. */ + +static tree +coro_make_frame_entry (tree *field_list, const char *name, tree fld_type, + location_t loc) +{ + tree id = get_identifier (name); + tree decl = build_decl (loc, FIELD_DECL, id, fld_type); + DECL_CHAIN (decl) = *field_list; + *field_list = decl; + return id; +} + +struct susp_frame_data +{ + tree *field_list; + tree handle_type; + hash_set captured_temps; + vec *to_replace; + vec *block_stack; + unsigned count; + unsigned saw_awaits; + bool captures_temporary; +}; + +/* Helper to return the type of an awaiter's await_suspend() method. + We start with the result of the build method call, which will be either + a call expression (void, bool) or a target expressions (handle). */ + +static tree +get_await_suspend_return_type (tree aw_expr) +{ + tree susp_fn = TREE_VEC_ELT (TREE_OPERAND (aw_expr, 3), 1); + if (TREE_CODE (susp_fn) == CALL_EXPR) + { + susp_fn = CALL_EXPR_FN (susp_fn); + if (TREE_CODE (susp_fn) == ADDR_EXPR) + susp_fn = TREE_OPERAND (susp_fn, 0); + return TREE_TYPE (TREE_TYPE (susp_fn)); + } + else if (TREE_CODE (susp_fn) == TARGET_EXPR) + return TREE_TYPE (susp_fn); + return TREE_TYPE (susp_fn); +} + +/* Walk the sub-tree looking for call expressions that both capture + references and have compiler-temporaries as parms. */ + +static tree +captures_temporary (tree *stmt, int *do_subtree, void *d) +{ + /* Stop recursing if we see an await expression, the subtrees + of that will be handled when it it processed. */ + if (TREE_CODE (*stmt) == CO_AWAIT_EXPR || TREE_CODE (*stmt) == CO_YIELD_EXPR) + { + *do_subtree = 0; + return NULL_TREE; + } + + /* We're only interested in calls. */ + if (TREE_CODE (*stmt) != CALL_EXPR) + return NULL_TREE; + + /* Does this call capture references? + Strip the ADDRESS_EXPR to get the fn decl and inspect it. */ + tree fn = TREE_OPERAND (CALL_EXPR_FN (*stmt), 0); + bool is_meth = TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE; + tree arg = TYPE_ARG_TYPES (TREE_TYPE (fn)); + unsigned offset = 3; + for (unsigned anum = 0; arg != NULL; arg = TREE_CHAIN (arg), anum++) + { + tree parm_type = TREE_VALUE (arg); + if (anum == 0 && is_meth && INDIRECT_TYPE_P (parm_type)) + { + /* Account for 'this' when the fn is a method. Unless it + belongs to a CTOR or DTOR. */ + if (DECL_CONSTRUCTOR_P (fn) || DECL_DESTRUCTOR_P (fn)) + continue; + } + else if (!TYPE_REF_P (parm_type)) + /* If it's not a reference, we don't care. */ + continue; + + /* Fetch the value presented to the fn. */ + tree parm = TREE_OPERAND (*stmt, anum + offset); + + while (TREE_CODE (parm) == NOP_EXPR) + parm = TREE_OPERAND (parm, 0); + + /* We only care if we're taking the addr of a temporary. */ + if (TREE_CODE (parm) != ADDR_EXPR) + continue; + + parm = TREE_OPERAND (parm, 0); + if (TREE_CODE (parm) == VAR_DECL && !DECL_ARTIFICIAL (parm)) + /* This isn't a temporary... */ + continue; + + if (TREE_CODE (parm) == PARM_DECL) + /* .. nor is this... */ + continue; + + if (TREE_CODE (parm) == TARGET_EXPR) + { + /* We're taking the address of a temporary and using it as a ref. */ + tree tvar = TREE_OPERAND (parm, 0); + gcc_checking_assert (DECL_ARTIFICIAL (tvar)); + + susp_frame_data *data = (susp_frame_data *) d; + data->captures_temporary = true; + /* Record this one so we don't duplicate, and on the first + occurrence note the target expr to be replaced. */ + if (!data->captured_temps.add (tvar)) + vec_safe_push (data->to_replace, parm); + /* Now see if the initializer contains any more cases. */ + hash_set visited; + tree res = cp_walk_tree (&TREE_OPERAND (parm, 1), + captures_temporary, d, &visited); + if (res) + return res; + /* Otherwise, we're done with sub-trees for this. */ + } + else if (TREE_CODE (parm) == CO_AWAIT_EXPR) + { + /* CO_AWAIT expressions behave in a similar manner to target + expressions when the await_resume call is contained in one. */ + tree awr = TREE_OPERAND (parm, 3); /* call vector. */ + awr = TREE_VEC_ELT (awr, 2); /* resume call. */ + if (TREE_CODE (awr) == TARGET_EXPR) + { + tree tvar = TREE_OPERAND (awr, 0); + gcc_checking_assert (DECL_ARTIFICIAL (tvar)); + + susp_frame_data *data = (susp_frame_data *) d; + data->captures_temporary = true; + /* Use this as a place-holder. */ + if (!data->captured_temps.add (tvar)) + vec_safe_push (data->to_replace, parm); + } + /* We will walk the sub-trees of this co_await separately. */ + } + else + gcc_unreachable (); + } + /* As far as it's necessary, we've walked the subtrees of the call + expr. */ + do_subtree = 0; + return NULL_TREE; +} + +/* If this is an await, then register it and decide on what coro + frame storage is needed. + If this is a co_yield (which embeds an await), drop the yield + and record the await (the yield was kept for diagnostics only). */ + +static tree +register_awaits (tree *stmt, int *do_subtree ATTRIBUTE_UNUSED, void *d) +{ + susp_frame_data *data = (susp_frame_data *) d; + + if (TREE_CODE (*stmt) != CO_AWAIT_EXPR && TREE_CODE (*stmt) != CO_YIELD_EXPR) + return NULL_TREE; + + /* co_yield is syntactic sugar, re-write it to co_await. */ + tree aw_expr = *stmt; + location_t aw_loc = EXPR_LOCATION (aw_expr); /* location of the co_xxxx. */ + if (TREE_CODE (aw_expr) == CO_YIELD_EXPR) + { + aw_expr = TREE_OPERAND (aw_expr, 1); + *stmt = aw_expr; + } + + /* Count how many awaits full expression contains. This is not the same + as the counter used for the function-wide await point number. */ + data->saw_awaits++; + + /* The required field has the same type as the proxy stored in the + await expr. */ + tree aw_field_type = TREE_TYPE (TREE_OPERAND (aw_expr, 1)); + + size_t bufsize = sizeof ("__aw_s.") + 10; + char *buf = (char *) alloca (bufsize); + snprintf (buf, bufsize, "__aw_s.%d", data->count); + tree aw_field_nam + = coro_make_frame_entry (data->field_list, buf, aw_field_type, aw_loc); + + /* Find out what we have to do with the awaiter's suspend method (this + determines if we need somewhere to stash the suspend method's handle). + Cache the result of this in the suspend point info. + [expr.await] + (5.1) If the result of await-ready is false, the coroutine is considered + suspended. Then: + (5.1.1) If the type of await-suspend is std::coroutine_handle, + await-suspend.resume() is evaluated. + (5.1.2) if the type of await-suspend is bool, await-suspend is evaluated, + and the coroutine is resumed if the result is false. + (5.1.3) Otherwise, await-suspend is evaluated. + */ + tree susp_typ = get_await_suspend_return_type (aw_expr); + tree handle_field_nam; + if (VOID_TYPE_P (susp_typ) || TREE_CODE (susp_typ) == BOOLEAN_TYPE) + handle_field_nam = NULL_TREE; /* no handle is needed. */ + else + { + snprintf (buf, bufsize, "__aw_h.%u", data->count); + handle_field_nam + = coro_make_frame_entry (data->field_list, buf, susp_typ, aw_loc); + } + register_await_info (aw_expr, aw_field_type, aw_field_nam, susp_typ, + handle_field_nam); + + data->count++; /* Each await suspend context is unique. */ + + /* We now need to know if to take special action on lifetime extension + of temporaries captured by reference. This can only happen if such + a case appears in the initializer for the awaitable. The callback + records captured temporaries including subtrees of initializers. */ + hash_set visited; + tree res = cp_walk_tree (&TREE_OPERAND (aw_expr, 2), captures_temporary, d, + &visited); + return res; +} + +/* The gimplifier correctly extends the lifetime of temporaries captured + by reference (per. [class.temporary] (6.9) "A temporary object bound + to a reference parameter in a function call persists until the completion + of the full-expression containing the call"). However, that is not + sufficient to work across a suspension - and we need to promote such + temporaries to be regular vars that will then get a coro frame slot. + We don't want to incur the effort of checking for this unless we have + an await expression in the current full expression. */ + +static tree +maybe_promote_captured_temps (tree *stmt, void *d) +{ + susp_frame_data *awpts = (susp_frame_data *) d; + hash_set visited; + awpts->saw_awaits = 0; + + /* When register_awaits sees an await, it walks the initializer for + that await looking for temporaries captured by reference and notes + them in awpts->captured_temps. We only need to take any action + here if the statement contained any awaits, and any of those had + temporaries captured by reference in the initializers for their class. + */ + + tree res = cp_walk_tree (stmt, register_awaits, d, &visited); + if (!res && awpts->saw_awaits > 0 && !awpts->captured_temps.is_empty ()) + { + location_t sloc = EXPR_LOCATION (*stmt); + tree aw_bind + = build3_loc (sloc, BIND_EXPR, void_type_node, NULL, NULL, NULL); + tree aw_statement_current; + if (TREE_CODE (*stmt) == CLEANUP_POINT_EXPR) + aw_statement_current = TREE_OPERAND (*stmt, 0); + else + aw_statement_current = *stmt; + /* Collected the scope vars we need move the temps to regular. */ + tree aw_bind_body = push_stmt_list (); + tree varlist = NULL_TREE; + unsigned vnum = 0; + while (!awpts->to_replace->is_empty ()) + { + size_t bufsize = sizeof ("__aw_.tmp.") + 20; + char *buf = (char *) alloca (bufsize); + snprintf (buf, bufsize, "__aw_%d.tmp.%d", awpts->count, vnum); + tree to_replace = awpts->to_replace->pop (); + tree orig_temp; + if (TREE_CODE (to_replace) == CO_AWAIT_EXPR) + { + orig_temp = TREE_OPERAND (to_replace, 3); + orig_temp = TREE_VEC_ELT (orig_temp, 2); + orig_temp = TREE_OPERAND (orig_temp, 0); + } + else + orig_temp = TREE_OPERAND (to_replace, 0); + + tree var_type = TREE_TYPE (orig_temp); + gcc_assert (same_type_p (TREE_TYPE (to_replace), var_type)); + tree newvar + = build_lang_decl (VAR_DECL, get_identifier (buf), var_type); + DECL_CONTEXT (newvar) = DECL_CONTEXT (orig_temp); + if (DECL_SOURCE_LOCATION (orig_temp)) + sloc = DECL_SOURCE_LOCATION (orig_temp); + DECL_SOURCE_LOCATION (newvar) = sloc; + DECL_CHAIN (newvar) = varlist; + varlist = newvar; + tree stmt + = build2_loc (sloc, INIT_EXPR, var_type, newvar, to_replace); + stmt = coro_build_cvt_void_expr_stmt (stmt, sloc); + add_stmt (stmt); + proxy_replace pr = {to_replace, newvar}; + /* Replace all instances of that temp in the original expr. */ + cp_walk_tree (&aw_statement_current, replace_proxy, &pr, NULL); + } + /* What's left should be the original statement with any temporaries + broken out. */ + add_stmt (aw_statement_current); + BIND_EXPR_BODY (aw_bind) = pop_stmt_list (aw_bind_body); + awpts->captured_temps.empty (); + + BIND_EXPR_VARS (aw_bind) = nreverse (varlist); + tree b_block = make_node (BLOCK); + if (!awpts->block_stack->is_empty ()) + { + tree s_block = awpts->block_stack->last (); + if (s_block) + { + BLOCK_SUPERCONTEXT (b_block) = s_block; + BLOCK_CHAIN (b_block) = BLOCK_SUBBLOCKS (s_block); + BLOCK_SUBBLOCKS (s_block) = b_block; + } + } + BIND_EXPR_BLOCK (aw_bind) = b_block; + + *stmt = aw_bind; + } + return res; +} + +static tree +await_statement_walker (tree *stmt, int *do_subtree, void *d) +{ + tree res = NULL_TREE; + susp_frame_data *awpts = (susp_frame_data *) d; + + /* We might need to insert a new bind expression, and want to link it + into the correct scope, so keep a note of the current block scope. */ + if (TREE_CODE (*stmt) == BIND_EXPR) + { + tree *body = &BIND_EXPR_BODY (*stmt); + tree blk = BIND_EXPR_BLOCK (*stmt); + vec_safe_push (awpts->block_stack, blk); + + if (TREE_CODE (*body) == STATEMENT_LIST) + { + tree_stmt_iterator i; + for (i = tsi_start (*body); !tsi_end_p (i); tsi_next (&i)) + { + tree *new_stmt = tsi_stmt_ptr (i); + if (STATEMENT_CLASS_P (*new_stmt) || !EXPR_P (*new_stmt) + || TREE_CODE (*new_stmt) == BIND_EXPR) + res = cp_walk_tree (new_stmt, await_statement_walker, d, NULL); + else + res = maybe_promote_captured_temps (new_stmt, d); + if (res) + return res; + } + *do_subtree = 0; /* Done subtrees. */ + } + else if (!STATEMENT_CLASS_P (*body) && EXPR_P (*body) + && TREE_CODE (*body) != BIND_EXPR) + { + res = maybe_promote_captured_temps (body, d); + *do_subtree = 0; /* Done subtrees. */ + } + awpts->block_stack->pop (); + } + else if (!STATEMENT_CLASS_P (*stmt) && EXPR_P (*stmt) + && TREE_CODE (*stmt) != BIND_EXPR) + { + res = maybe_promote_captured_temps (stmt, d); + *do_subtree = 0; /* Done subtrees. */ + } + /* If it wasn't a statement list, or a single statement, continue. */ + return res; +} + +/* For figuring out what param usage we have. */ + +struct param_frame_data +{ + tree *field_list; + hash_map *param_uses; + location_t loc; + bool param_seen; +}; + +static tree +register_param_uses (tree *stmt, int *do_subtree ATTRIBUTE_UNUSED, void *d) +{ + param_frame_data *data = (param_frame_data *) d; + + if (TREE_CODE (*stmt) != PARM_DECL) + return NULL_TREE; + + bool existed; + param_info &parm = data->param_uses->get_or_insert (*stmt, &existed); + gcc_checking_assert (existed); + + if (parm.field_id == NULL_TREE) + { + tree actual_type = TREE_TYPE (*stmt); + + if (!COMPLETE_TYPE_P (actual_type)) + actual_type = complete_type_or_else (actual_type, *stmt); + + if (TREE_CODE (actual_type) == REFERENCE_TYPE) + actual_type = build_pointer_type (TREE_TYPE (actual_type)); + + parm.frame_type = actual_type; + tree pname = DECL_NAME (*stmt); + size_t namsize = sizeof ("__parm.") + IDENTIFIER_LENGTH (pname) + 1; + char *buf = (char *) alloca (namsize); + snprintf (buf, namsize, "__parm.%s", IDENTIFIER_POINTER (pname)); + parm.field_id + = coro_make_frame_entry (data->field_list, buf, actual_type, data->loc); + vec_alloc (parm.body_uses, 4); + parm.body_uses->quick_push (stmt); + data->param_seen = true; + } + else + parm.body_uses->safe_push (stmt); + + return NULL_TREE; +} + +/* For figuring out what local variable usage we have. */ + +struct local_vars_frame_data +{ + tree *field_list; + hash_map *local_var_uses; + vec *captures; + unsigned int nest_depth, bind_indx; + location_t loc; + bool saw_capture; + bool local_var_seen; +}; + +static tree +register_local_var_uses (tree *stmt, int *do_subtree, void *d) +{ + local_vars_frame_data *lvd = (local_vars_frame_data *) d; + + /* As we enter a bind expression - record the vars there and then recurse. + As we exit drop the nest depth. + The bind index is a growing count of how many bind indices we've seen. + We build a space in the frame for each local var. + */ + if (TREE_CODE (*stmt) == BIND_EXPR) + { + lvd->bind_indx++; + lvd->nest_depth++; + tree lvar; + for (lvar = BIND_EXPR_VARS (*stmt); lvar != NULL; + lvar = DECL_CHAIN (lvar)) + { + bool existed; + local_var_info &local_var + = lvd->local_var_uses->get_or_insert (lvar, &existed); + gcc_checking_assert (!existed); + tree lvtype = TREE_TYPE (lvar); + tree lvname = DECL_NAME (lvar); + bool captured = is_normal_capture_proxy (lvar); + /* Make names depth+index unique, so that we can support nested + scopes with identically named locals. */ + char *buf; + size_t namsize = sizeof ("__lv...") + 18; + const char *nm = (captured ? "cp" : "lv"); + if (lvname != NULL_TREE) + { + namsize += IDENTIFIER_LENGTH (lvname); + buf = (char *) alloca (namsize); + snprintf (buf, namsize, "__%s.%u.%u.%s", nm, lvd->bind_indx, + lvd->nest_depth, IDENTIFIER_POINTER (lvname)); + } + else + { + namsize += 10; // 'D' followed by an unsigned. + buf = (char *) alloca (namsize); + snprintf (buf, namsize, "__%s.%u.%u.D%u", nm, lvd->bind_indx, + lvd->nest_depth, DECL_UID (lvar)); + } + /* TODO: Figure out if we should build a local type that has any + excess alignment or size from the original decl. */ + local_var.field_id + = coro_make_frame_entry (lvd->field_list, buf, lvtype, lvd->loc); + local_var.def_loc = DECL_SOURCE_LOCATION (lvar); + local_var.frame_type = lvtype; + local_var.field_idx = NULL_TREE; + if (captured) + { + gcc_checking_assert (DECL_INITIAL (lvar) == NULL_TREE); + local_var.captured = lvar; + lvd->captures->safe_push (local_var); + lvd->saw_capture = true; + } + else + local_var.captured = NULL; + lvd->local_var_seen = true; + /* We don't walk any of the local var sub-trees, they won't contain + any bind exprs. */ + } + cp_walk_tree (&BIND_EXPR_BODY (*stmt), register_local_var_uses, d, NULL); + *do_subtree = 0; /* We've done this. */ + lvd->nest_depth--; + } + return NULL_TREE; +} + +/* 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 (*__resume)(_R_frame *); + void (*__destroy)(_R_frame *); + coro1::promise_type __p; + bool frame_needs_free; // free the coro frame mem if set. + short __resume_at; // this is where clang puts it - but it's a smaller entity. + coro1::suspend_never_prt __is; + (maybe) handle_type i_hand; + coro1::suspend_always_prt __fs; + (maybe) handle_type f_hand; + (maybe) parameters used in the body. + (maybe) local variables saved + (maybe) trailing space. + }; */ + +bool +morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer) +{ + gcc_checking_assert (orig && TREE_CODE (orig) == FUNCTION_DECL); + + if (!coro_function_valid_p (orig)) + return false; + + /* We can't validly get here with an empty statement list, since there's no + way for the FE to decide it's a coroutine in the absence of any code. */ + tree fnbody = pop_stmt_list (DECL_SAVED_TREE (orig)); + if (fnbody == NULL_TREE) + 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); + gcc_rich_location fn_start_loc (fn_start); + + /* Initial processing of the captured body. + If we have no expressions or just an error then punt. */ + tree body_start = expr_first (fnbody); + if (body_start == NULL_TREE || body_start == error_mark_node) + { + DECL_SAVED_TREE (orig) = push_stmt_list (); + append_to_statement_list (DECL_SAVED_TREE (orig), &fnbody); + return false; + } + + /* So, we've tied off the original body. Now start the replacement. + If we encounter a fatal error we might return a now-empty body. + TODO: determine if it would help to restore the original. + determine if looking for more errors in coro_function_valid_p() + and stashing types is a better solution. + */ + + tree newbody = push_stmt_list (); + DECL_SAVED_TREE (orig) = newbody; + + /* If our original body is noexcept, then that's what we apply to our + generated functions. Remember that we're NOEXCEPT and fish out the + contained list (we tied off to the top level already). */ + bool is_noexcept = TREE_CODE (body_start) == MUST_NOT_THROW_EXPR; + if (is_noexcept) + { + /* Simplified abstract from begin_eh_spec_block, since we already + know the outcome. */ + fnbody = TREE_OPERAND (body_start, 0); /* Stash the original... */ + add_stmt (body_start); /* ... and start the new. */ + TREE_OPERAND (body_start, 0) = push_stmt_list (); + } + + /* 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)); + gcc_assert (!VOID_TYPE_P (fn_return_type)); + tree handle_type = get_coroutine_handle_type (orig); + tree promise_type = get_coroutine_promise_type (orig); + + /* 2. Types we need to define or look up. */ + + /* 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. */ + suspend_points = new hash_map; + + /* Initial and final suspend types are special in that the co_awaits for + them are synthetic. We need to find the type for each awaiter from + the coroutine promise. */ + tree initial_await = build_init_or_final_await (fn_start, false); + if (initial_await == error_mark_node) + return false; + /* The type of the frame var for this is the type of its temp proxy. */ + tree initial_suspend_type = TREE_TYPE (TREE_OPERAND (initial_await, 1)); + + tree final_await = build_init_or_final_await (fn_start, true); + if (final_await == error_mark_node) + return false; + + /* The type of the frame var for this is the type of its temp proxy. */ + tree final_suspend_type = TREE_TYPE (TREE_OPERAND (final_await, 1)); + + tree fr_name = get_fn_local_identifier (orig, "frame"); + tree coro_frame_type = xref_tag (record_type, fr_name, ts_current, false); + DECL_CONTEXT (TYPE_NAME (coro_frame_type)) = 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); + + /* Declare the actor function. */ + tree actor_name = get_fn_local_identifier (orig, "actor"); + tree actor = build_lang_decl (FUNCTION_DECL, actor_name, act_des_fn_type); + DECL_CONTEXT (actor) = DECL_CONTEXT (orig); + DECL_INITIAL (actor) = error_mark_node; + + /* Declare the destroyer function. */ + tree destr_name = get_fn_local_identifier (orig, "destroy"); + tree destroy = build_lang_decl (FUNCTION_DECL, destr_name, act_des_fn_type); + DECL_CONTEXT (destroy) = DECL_CONTEXT (orig); + DECL_INITIAL (destroy) = error_mark_node; + + /* Build our dummy coro frame layout. */ + coro_frame_type = begin_class_definition (coro_frame_type); + + tree field_list = NULL_TREE; + tree resume_name + = coro_make_frame_entry (&field_list, "__resume", act_des_fn_ptr, fn_start); + tree destroy_name = coro_make_frame_entry (&field_list, "__destroy", + act_des_fn_ptr, fn_start); + tree promise_name + = coro_make_frame_entry (&field_list, "__p", promise_type, fn_start); + tree fnf_name = coro_make_frame_entry (&field_list, "__frame_needs_free", + boolean_type_node, fn_start); + tree resume_idx_name + = coro_make_frame_entry (&field_list, "__resume_at", + short_unsigned_type_node, fn_start); + + /* We need a handle to this coroutine, which is passed to every + await_suspend(). There's no point in creating it over and over. */ + (void) coro_make_frame_entry (&field_list, "__self_h", handle_type, fn_start); + + /* Initial suspend is mandated. */ + tree init_susp_name = coro_make_frame_entry (&field_list, "__aw_s.is", + initial_suspend_type, fn_start); + + /* Figure out if we need a saved handle from the awaiter type. */ + tree ret_typ = get_await_suspend_return_type (initial_await); + tree init_hand_name; + if (VOID_TYPE_P (ret_typ) || TREE_CODE (ret_typ) == BOOLEAN_TYPE) + init_hand_name = NULL_TREE; /* no handle is needed. */ + else + { + init_hand_name + = coro_make_frame_entry (&field_list, "__ih", ret_typ, fn_start); + } + + register_await_info (initial_await, initial_suspend_type, init_susp_name, + ret_typ, init_hand_name); + + /* 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 + = {&field_list, handle_type, hash_set (), NULL, NULL, 0, 0, false}; + body_aw_points.to_replace = make_tree_vector (); + body_aw_points.block_stack = make_tree_vector (); + cp_walk_tree (&fnbody, await_statement_walker, &body_aw_points, NULL); + + /* Final suspend is mandated. */ + tree fin_susp_name = coro_make_frame_entry (&field_list, "__aw_s.fs", + final_suspend_type, fn_start); + + ret_typ = get_await_suspend_return_type (final_await); + tree fin_hand_name; + if (VOID_TYPE_P (ret_typ) || TREE_CODE (ret_typ) == BOOLEAN_TYPE) + fin_hand_name = NULL_TREE; /* no handle is needed. */ + else + { + fin_hand_name + = coro_make_frame_entry (&field_list, "__fh", ret_typ, fn_start); + } + + register_await_info (final_await, final_suspend_type, fin_susp_name, + void_type_node, fin_hand_name); + + /* 3. Now add in fields for function params (if there are any) that are used + within the function body. This is conservative; we can't tell at this + stage if such uses might be optimized away, or if they might turn out not + to persist across any suspend points. Of course, even if they don't + persist across suspend points, when the actor is out of line the saved + frame version is still needed. */ + hash_map *param_uses = NULL; + if (DECL_ARGUMENTS (orig)) + { + /* Build a hash map with an entry for each param. + The key is the param tree. + Then we have an entry for the frame field name. + Then a cache for the field ref when we come to use it. + Then a tree list of the uses. + The second two entries start out empty - and only get populated + when we see uses. */ + param_uses = new hash_map; + + for (tree arg = DECL_ARGUMENTS (orig); arg != NULL; + arg = DECL_CHAIN (arg)) + { + bool existed; + param_info &parm = param_uses->get_or_insert (arg, &existed); + gcc_checking_assert (!existed); + parm.field_id = NULL_TREE; + parm.body_uses = NULL; + } + + param_frame_data param_data + = {&field_list, param_uses, fn_start, false}; + /* We want to record every instance of param's use, so don't include + a 'visited' hash_set. */ + cp_walk_tree (&fnbody, register_param_uses, ¶m_data, NULL); + + /* If no uses for any param were seen, act as if there were no + params (it could be that they are only used to construct the + promise). */ + if (!param_data.param_seen) + { + delete param_uses; + param_uses = NULL; + } + } + + /* 4. Now make space for local vars, this is conservative again, and we + would expect to delete unused entries later. */ + hash_map local_var_uses; + auto_vec captures; + + local_vars_frame_data local_vars_data + = {&field_list, &local_var_uses, &captures, 0, 0, fn_start, false, false}; + 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); + + /* Ramp: */ + /* 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 empty_list = build_empty_stmt (fn_start); + + tree coro_fp = build_lang_decl (VAR_DECL, get_identifier ("coro.frameptr"), + coro_frame_ptr); + tree varlist = coro_fp; + local_var_info *cap; + if (!captures.is_empty()) + for (int ix = 0; captures.iterate (ix, &cap); ix++) + { + if (cap->field_id == NULL_TREE) + continue; + tree t = cap->captured; + DECL_CHAIN (t) = varlist; + varlist = t; + } + + /* Collected the scope vars we need ... only one for now. */ + BIND_EXPR_VARS (ramp_bind) = nreverse (varlist); + + /* We're now going to create a new top level scope block for the ramp + function. */ + tree top_block = make_node (BLOCK); + + BIND_EXPR_BLOCK (ramp_bind) = top_block; + BLOCK_VARS (top_block) = BIND_EXPR_VARS (ramp_bind); + BLOCK_SUBBLOCKS (top_block) = NULL_TREE; + + /* The decl_expr for the coro frame pointer, initialize to zero so that we + can pass it to the IFN_CO_FRAME (since there's no way to pass a type, + directly apparently). This avoids a "used uninitialized" warning. */ + tree r = build_stmt (fn_start, DECL_EXPR, coro_fp); + tree zeroinit = build1 (CONVERT_EXPR, coro_frame_ptr, integer_zero_node); + r = build2 (INIT_EXPR, TREE_TYPE (coro_fp), coro_fp, zeroinit); + r = coro_build_cvt_void_expr_stmt (r, fn_start); + add_stmt (r); + + /* We are going to copy the behavior of clang w.r.t to failed allocation + of the coroutine frame. + 1. If the promise has a 'get_return_object_on_allocation_failure()' + method, then we use a nothrow new and check the return value, calling + the method on failure to initialize an early return. + 2. Otherwise, we call new and the ramp is expected to terminate with an + unhandled exception in the case of failure to allocate. + + The get_return_object_on_allocation_failure() must be a static method. + */ + tree grooaf_meth + = lookup_promise_method (orig, coro_gro_on_allocation_fail_identifier, + fn_start, /*musthave=*/ false); + + /* The CO_FRAME internal function is a mechanism to allow the middle end + to adjust the allocation in response to optimisations. We provide the + current conservative estimate of the frame size (as per the current) + computed layout. */ + tree resizeable + = build_call_expr_internal_loc (fn_start, IFN_CO_FRAME, size_type_node, 2, + TYPE_SIZE_UNIT (coro_frame_type), coro_fp); + + /* We need to adjust the operator new call as per the description above when + there is a return on allocation fail function provided in the promise. */ + tree grooaf = NULL_TREE; + vec *arglist; + vec_alloc (arglist, 2); + arglist->quick_push (resizeable); + if (grooaf_meth && BASELINK_P (grooaf_meth)) + { + tree fn = BASELINK_FUNCTIONS (grooaf_meth); + if (TREE_CODE (fn) == FUNCTION_DECL && DECL_STATIC_FUNCTION_P (fn)) + { + grooaf = build_call_expr_loc (fn_start, fn, 0); + TREE_USED (fn) = 1; + } + tree nth_ns = lookup_qualified_name (std_node, get_identifier ("nothrow"), + 0, /*complain=*/ true, false); + arglist->quick_push (nth_ns); + } + + /* Allocate the frame. */ + + tree nwname = ovl_op_identifier (false, NEW_EXPR); + /* The user can (optionally) provide an allocation function in the promise + type, it's not a failure for it to be absent. */ + tree fns = lookup_promise_method (orig, nwname, fn_start, + /*musthave=*/ false); + tree new_fn = NULL_TREE; + if (fns && fns != error_mark_node) + { + new_fn = lookup_arg_dependent (nwname, fns, arglist); + if (OVL_P (new_fn)) + new_fn = OVL_FIRST (new_fn); + else + new_fn = BASELINK_FUNCTIONS (new_fn); + + gcc_checking_assert (DECL_STATIC_FUNCTION_P (new_fn)); + TREE_USED (new_fn) = 1; + new_fn = build_call_expr_loc_vec (fn_start, new_fn, arglist); + } + + /* If that fails, then fall back to the global operator new. */ + if (new_fn == NULL_TREE || new_fn == error_mark_node) + { + fns =lookup_name_real (nwname, 0, 1, /*block_p=*/true, 0, 0); + new_fn = lookup_arg_dependent (nwname, fns, arglist); + new_fn = build_new_function_call (new_fn, &arglist, /*complain=*/ true); + } + + tree allocated = build1 (CONVERT_EXPR, coro_frame_ptr, new_fn); + r = build2 (INIT_EXPR, TREE_TYPE (coro_fp), coro_fp, allocated); + r = coro_build_cvt_void_expr_stmt (r, fn_start); + add_stmt (r); + + /* If the user provided a method to return an object on alloc fail, then + check the returned pointer and call the func if it's null. + Otherwise, no check, and we fail for noexcept/fno-exceptions cases. */ + + if (grooaf) + { + tree cfra_label + = create_named_label_with_ctx (fn_start, "coro.frame.active", + current_scope ()); + tree early_ret_list = NULL; + /* init the retval using the user's func. */ + r = build2 (INIT_EXPR, TREE_TYPE (DECL_RESULT (orig)), DECL_RESULT (orig), + grooaf); + r = coro_build_cvt_void_expr_stmt (r, fn_start); + append_to_statement_list (r, &early_ret_list); + // We know it's the correct type. + r = DECL_RESULT (orig); + r = build_stmt (fn_start, RETURN_EXPR, r); + TREE_NO_WARNING (r) |= 1; + r = maybe_cleanup_point_expr_void (r); + append_to_statement_list (r, &early_ret_list); + + tree goto_st = NULL; + r = build1 (GOTO_EXPR, void_type_node, cfra_label); + append_to_statement_list (r, &goto_st); + + tree ckk = build1 (CONVERT_EXPR, coro_frame_ptr, integer_zero_node); + tree ckz = build2 (EQ_EXPR, boolean_type_node, coro_fp, ckk); + r = build3 (COND_EXPR, void_type_node, ckz, early_ret_list, empty_list); + add_stmt (r); + + cfra_label = build_stmt (fn_start, LABEL_EXPR, cfra_label); + add_stmt (cfra_label); + } + + /* deref the frame pointer, to use in member access code. */ + tree deref_fp = build_x_arrow (fn_start, coro_fp, tf_warning_or_error); + + /* For now, we always assume that this needs destruction, there's no impl. + for frame allocation elision. */ + tree fnf_m + = lookup_member (coro_frame_type, fnf_name, 1, 0, tf_warning_or_error); + tree fnf_x = build_class_member_access_expr (deref_fp, fnf_m, NULL_TREE, + false, tf_warning_or_error); + r = build2 (INIT_EXPR, boolean_type_node, fnf_x, boolean_true_node); + r = coro_build_cvt_void_expr_stmt (r, fn_start); + add_stmt (r); + + /* Put the resumer and destroyer functions in. */ + + tree actor_addr = build1 (ADDR_EXPR, act_des_fn_ptr, actor); + tree resume_m + = lookup_member (coro_frame_type, resume_name, + /*protect*/ 1, /*want_type*/ 0, tf_warning_or_error); + tree resume_x = build_class_member_access_expr (deref_fp, resume_m, NULL_TREE, + false, tf_warning_or_error); + r = build2_loc (fn_start, INIT_EXPR, act_des_fn_ptr, resume_x, actor_addr); + r = coro_build_cvt_void_expr_stmt (r, fn_start); + add_stmt (r); + + tree destroy_addr = build1 (ADDR_EXPR, act_des_fn_ptr, destroy); + tree destroy_m + = lookup_member (coro_frame_type, destroy_name, + /*protect*/ 1, /*want_type*/ 0, tf_warning_or_error); + tree destroy_x + = build_class_member_access_expr (deref_fp, destroy_m, NULL_TREE, false, + tf_warning_or_error); + r = build2_loc (fn_start, INIT_EXPR, act_des_fn_ptr, destroy_x, destroy_addr); + r = coro_build_cvt_void_expr_stmt (r, fn_start); + add_stmt (r); + + /* Set up the promise. */ + tree promise_m + = lookup_member (coro_frame_type, promise_name, + /*protect*/ 1, /*want_type*/ 0, tf_warning_or_error); + + tree p = build_class_member_access_expr (deref_fp, promise_m, NULL_TREE, + false, tf_warning_or_error); + + if (TYPE_NEEDS_CONSTRUCTING (promise_type)) + { + /* Do a placement new constructor for the promise type (we never call + the new operator, just the constructor on the object in place in the + frame). + + First try to find a constructor with the same parameter list as the + original function (if it has params), failing that find a constructor + with no parameter list. + */ + + if (DECL_ARGUMENTS (orig)) + { + vec *args = make_tree_vector (); + tree arg; + for (arg = DECL_ARGUMENTS (orig); arg != NULL; arg = DECL_CHAIN (arg)) + vec_safe_push (args, arg); + r = build_special_member_call (p, complete_ctor_identifier, &args, + promise_type, LOOKUP_NORMAL, tf_none); + release_tree_vector (args); + } + else + r = NULL_TREE; + + if (r == NULL_TREE || r == error_mark_node) + r = build_special_member_call (p, complete_ctor_identifier, NULL, + promise_type, LOOKUP_NORMAL, + tf_warning_or_error); + + r = coro_build_cvt_void_expr_stmt (r, fn_start); + add_stmt (r); + } + + /* Copy in any of the function params we found to be used. + Param types with non-trivial dtors will have to be moved into position + and the dtor run before the frame is freed. */ + vec *param_dtor_list = NULL; + if (DECL_ARGUMENTS (orig) && param_uses != NULL) + { + tree arg; + for (arg = DECL_ARGUMENTS (orig); arg != NULL; arg = DECL_CHAIN (arg)) + { + bool existed; + param_info &parm = param_uses->get_or_insert (arg, &existed); + if (parm.field_id == NULL_TREE) + continue; /* Wasn't used. */ + + tree fld_ref = lookup_member (coro_frame_type, parm.field_id, + /*protect*/ 1, /*want_type*/ 0, + tf_warning_or_error); + tree fld_idx + = build_class_member_access_expr (deref_fp, fld_ref, NULL_TREE, + false, tf_warning_or_error); + + if (TYPE_NEEDS_CONSTRUCTING (parm.frame_type)) + { + vec *p_in; + if (TYPE_REF_P (DECL_ARG_TYPE (arg)) + && (CLASSTYPE_LAZY_MOVE_CTOR (parm.frame_type) + || CLASSTYPE_LAZY_MOVE_ASSIGN (parm.frame_type) + || classtype_has_move_assign_or_move_ctor_p + (parm.frame_type, /* user-declared */ true))) + p_in = make_tree_vector_single (rvalue (arg)); + else + p_in = make_tree_vector_single (arg); + /* Construct in place or move as relevant. */ + r = build_special_member_call (fld_idx, complete_ctor_identifier, + &p_in, parm.frame_type, + LOOKUP_NORMAL, + tf_warning_or_error); + release_tree_vector (p_in); + if (param_dtor_list == NULL) + param_dtor_list = make_tree_vector (); + vec_safe_push (param_dtor_list, parm.field_id); + } + else + { + if (!same_type_p (parm.frame_type, DECL_ARG_TYPE (arg))) + r = build1_loc (DECL_SOURCE_LOCATION (arg), CONVERT_EXPR, + parm.frame_type, arg); + else + r = arg; + r = build_modify_expr (fn_start, fld_idx, parm.frame_type, + INIT_EXPR, DECL_SOURCE_LOCATION (arg), r, + TREE_TYPE (r)); + } + r = coro_build_cvt_void_expr_stmt (r, fn_start); + add_stmt (r); + } + } + + vec *captures_dtor_list = NULL; + while (!captures.is_empty()) + { + local_var_info cap = captures.pop(); + if (cap.field_id == NULL_TREE) + continue; + + tree fld_ref = lookup_member (coro_frame_type, cap.field_id, + /*protect*/ 1, /*want_type*/ 0, + tf_warning_or_error); + tree fld_idx + = build_class_member_access_expr (deref_fp, fld_ref, NULL_TREE, + false, tf_warning_or_error); + + tree cap_type = cap.frame_type; + + /* When we have a reference, we do not want to change the referenced + item, but actually to set the reference to the proxy var. */ + if (REFERENCE_REF_P (fld_idx)) + fld_idx = TREE_OPERAND (fld_idx, 0); + + if (TYPE_NEEDS_CONSTRUCTING (cap_type)) + { + vec *p_in; + if (TYPE_REF_P (cap_type) + && (CLASSTYPE_LAZY_MOVE_CTOR (cap_type) + || CLASSTYPE_LAZY_MOVE_ASSIGN (cap_type) + || classtype_has_move_assign_or_move_ctor_p + (cap_type, /* user-declared */ true))) + p_in = make_tree_vector_single (rvalue (cap.captured)); + else + p_in = make_tree_vector_single (cap.captured); + /* Construct in place or move as relevant. */ + r = build_special_member_call (fld_idx, complete_ctor_identifier, + &p_in, cap_type, LOOKUP_NORMAL, + tf_warning_or_error); + release_tree_vector (p_in); + if (captures_dtor_list == NULL) + captures_dtor_list = make_tree_vector (); + vec_safe_push (captures_dtor_list, cap.field_id); + } + else + { + if (!same_type_p (cap_type, TREE_TYPE (cap.captured))) + r = build1_loc (DECL_SOURCE_LOCATION (cap.captured), CONVERT_EXPR, + cap_type, cap.captured); + else + r = cap.captured; + r = build_modify_expr (fn_start, fld_idx, cap_type, + INIT_EXPR, DECL_SOURCE_LOCATION (cap.captured), + r, TREE_TYPE (r)); + } + r = coro_build_cvt_void_expr_stmt (r, fn_start); + add_stmt (r); + } + + /* Set up a new bind context for the GRO. */ + tree gro_context_bind = build3 (BIND_EXPR, void_type_node, NULL, NULL, NULL); + /* Make and connect the scope blocks. */ + tree gro_block = make_node (BLOCK); + BLOCK_SUPERCONTEXT (gro_block) = top_block; + BLOCK_SUBBLOCKS (top_block) = gro_block; + BIND_EXPR_BLOCK (gro_context_bind) = gro_block; + add_stmt (gro_context_bind); + + tree gro_meth = lookup_promise_method (orig, + coro_get_return_object_identifier, + fn_start, /*musthave=*/ true ); + tree get_ro + = build_new_method_call (p, gro_meth, NULL, NULL_TREE, LOOKUP_NORMAL, NULL, + tf_warning_or_error); + /* 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); + DECL_SAVED_TREE (orig) = newbody; + return false; + } + + tree gro_context_body = push_stmt_list (); + tree gro, gro_bind_vars; + if (same_type_p (TREE_TYPE (get_ro), fn_return_type)) + { + gro = DECL_RESULT (orig); + gro_bind_vars = NULL_TREE; // We don't need a separate var. + } + else + { + gro = build_lang_decl (VAR_DECL, get_identifier ("coro.gro"), + TREE_TYPE (TREE_OPERAND (get_ro, 0))); + DECL_CONTEXT (gro) = current_scope (); + r = build_stmt (fn_start, DECL_EXPR, gro); + add_stmt (r); + gro_bind_vars = gro; // We need a temporary var. + } + + // init our actual var. + r = build2_loc (fn_start, INIT_EXPR, TREE_TYPE (gro), gro, get_ro); + r = coro_build_cvt_void_expr_stmt (r, fn_start); + add_stmt (r); + + /* Initialize the resume_idx_name to 0, meaning "not started". */ + tree resume_idx_m + = lookup_member (coro_frame_type, resume_idx_name, + /*protect*/ 1, /*want_type*/ 0, tf_warning_or_error); + tree resume_idx + = build_class_member_access_expr (deref_fp, resume_idx_m, NULL_TREE, false, + tf_warning_or_error); + r = build_int_cst (short_unsigned_type_node, 0); + r = build2_loc (fn_start, INIT_EXPR, short_unsigned_type_node, resume_idx, r); + r = coro_build_cvt_void_expr_stmt (r, fn_start); + add_stmt (r); + + /* So .. call the actor .. */ + r = build_call_expr_loc (fn_start, actor, 1, coro_fp); + r = maybe_cleanup_point_expr_void (r); + add_stmt (r); + + /* Switch to using 'input_location' as the loc, since we're now more + logically doing things related to the end of the function. */ + /* done, we just need the return value. */ + bool no_warning; + if (same_type_p (TREE_TYPE (gro), fn_return_type)) + { + /* Already got the result. */ + r = check_return_expr (DECL_RESULT (orig), &no_warning); + } + else + { + // construct the return value with a single GRO param. + vec *args = make_tree_vector_single (gro); + r = build_special_member_call (DECL_RESULT (orig), + complete_ctor_identifier, &args, + fn_return_type, LOOKUP_NORMAL, + tf_warning_or_error); + r = coro_build_cvt_void_expr_stmt (r, input_location); + add_stmt (r); + release_tree_vector (args); + } + + r = build_stmt (input_location, RETURN_EXPR, DECL_RESULT (orig)); + TREE_NO_WARNING (r) |= no_warning; + r = maybe_cleanup_point_expr_void (r); + add_stmt (r); + BIND_EXPR_VARS (gro_context_bind) = gro_bind_vars; + BIND_EXPR_BODY (gro_context_bind) = pop_stmt_list (gro_context_body); + BIND_EXPR_BODY (ramp_bind) = pop_stmt_list (ramp_body); + + /* We know the "real" promise and have a frame layout with a slot for each + suspend point, so we can build an actor function (which contains the + functionality for both 'resume' and 'destroy'). + + wrap the function body in a try {} catch (...) {} block, if exceptions + are enabled. */ + + /* First make a new block for the body - that will be embedded in the + re-written function. */ + tree first = expr_first (fnbody); + bool orig_fn_has_outer_bind = false; + tree replace_blk = NULL_TREE; + if (first && TREE_CODE (first) == BIND_EXPR) + { + orig_fn_has_outer_bind = true; + tree block = BIND_EXPR_BLOCK (first); + replace_blk = make_node (BLOCK); + if (block) // missing block is probably an error. + { + gcc_assert (BLOCK_SUPERCONTEXT (block) == NULL_TREE); + gcc_assert (BLOCK_CHAIN (block) == NULL_TREE); + BLOCK_VARS (replace_blk) = BLOCK_VARS (block); + BLOCK_SUBBLOCKS (replace_blk) = BLOCK_SUBBLOCKS (block); + for (tree b = BLOCK_SUBBLOCKS (replace_blk); b; b = BLOCK_CHAIN (b)) + BLOCK_SUPERCONTEXT (b) = replace_blk; + } + BIND_EXPR_BLOCK (first) = replace_blk; + } + + if (flag_exceptions) + { + tree ueh_meth + = lookup_promise_method (orig, coro_unhandled_exception_identifier, + fn_start, /*musthave=*/ true); + /* Build promise.unhandled_exception(); */ + tree ueh + = build_new_method_call (p, ueh_meth, NULL, NULL_TREE, LOOKUP_NORMAL, + NULL, tf_warning_or_error); + + /* The try block is just the original function, there's no real + need to call any function to do this. */ + tree tcb = build_stmt (fn_start, TRY_BLOCK, NULL_TREE, NULL_TREE); + TRY_STMTS (tcb) = fnbody; + TRY_HANDLERS (tcb) = push_stmt_list (); + /* Mimic what the parser does for the catch. */ + tree handler = begin_handler (); + finish_handler_parms (NULL_TREE, handler); /* catch (...) */ + ueh = maybe_cleanup_point_expr_void (ueh); + add_stmt (ueh); + finish_handler (handler); + TRY_HANDLERS (tcb) = pop_stmt_list (TRY_HANDLERS (tcb)); + /* If the function starts with a BIND_EXPR, then we need to create + one here to contain the try-catch and to link up the scopes. */ + if (orig_fn_has_outer_bind) + { + tree tcb_bind = build3 (BIND_EXPR, void_type_node, NULL, NULL, NULL); + /* Make and connect the scope blocks. */ + tree tcb_block = make_node (BLOCK); + /* .. and connect it here. */ + BLOCK_SUPERCONTEXT (replace_blk) = tcb_block; + BLOCK_SUBBLOCKS (tcb_block) = replace_blk; + BIND_EXPR_BLOCK (tcb_bind) = tcb_block; + BIND_EXPR_BODY (tcb_bind) = tcb; + fnbody = tcb_bind; + } + else + fnbody = tcb; + } + else if (pedantic) + { + /* We still try to look for the promise method and warn if it's not + present. */ + tree ueh_meth + = lookup_promise_method (orig, coro_unhandled_exception_identifier, + fn_start, /*musthave=*/ false); + if (!ueh_meth || ueh_meth == error_mark_node) + warning_at (fn_start, 0, "no member named %qE in %qT", + coro_unhandled_exception_identifier, + get_coroutine_promise_type (orig)); + } + /* Else we don't check and don't care if the method is missing. */ + + /* Start to build the final functions. + + We push_deferring_access_checks to avoid these routines being seen as + nested by the middle end; we are doing the outlining here. */ + + push_deferring_access_checks (dk_no_check); + + /* Actor ... */ + build_actor_fn (fn_start, coro_frame_type, actor, fnbody, orig, param_uses, + &local_var_uses, param_dtor_list, initial_await, final_await, + body_aw_points.count); + + /* Destroyer ... */ + build_destroy_fn (fn_start, coro_frame_type, destroy, actor); + + pop_deferring_access_checks (); + + DECL_SAVED_TREE (orig) = newbody; + /* Link our new functions into the list. */ + TREE_CHAIN (destroy) = TREE_CHAIN (orig); + TREE_CHAIN (actor) = destroy; + TREE_CHAIN (orig) = actor; + + *resumer = actor; + *destroyer = destroy; + + delete suspend_points; + suspend_points = NULL; + return true; +} + +#include "gt-cp-coroutines.h" + diff --git a/gcc/cp/cp-objcp-common.c b/gcc/cp/cp-objcp-common.c index 42eb5ac033b..dfd8be978b4 100644 --- a/gcc/cp/cp-objcp-common.c +++ b/gcc/cp/cp-objcp-common.c @@ -551,6 +551,10 @@ cp_common_init_ts (void) MARK_TS_EXP (SIMPLE_REQ); MARK_TS_EXP (TYPE_REQ); + MARK_TS_EXP (CO_AWAIT_EXPR); + MARK_TS_EXP (CO_YIELD_EXPR); + MARK_TS_EXP (CO_RETURN_EXPR); + c_common_init_ts (); } diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def index 3f96da70e81..1454802bf68 100644 --- a/gcc/cp/cp-tree.def +++ b/gcc/cp/cp-tree.def @@ -574,6 +574,30 @@ DEFTREECODE (DISJ_CONSTR, "disj_constr", tcc_expression, 2) CHECK_CONSTR_ARGUMENTS are the template arguments */ DEFTREECODE (CHECK_CONSTR, "check_constr", tcc_expression, 2) +/* The co_await expression is used to support coroutines. + + Op 0 is the cast expresssion (potentially modified by the + promise "await_transform()" method). + Op1 is a proxy for the temp / coro frame slot 'e' value. + Op2 is the initialiser for Op1 (Op0, potentially modified by any + applicable 'co_await' operator). + Op3 is a vector of the [0] e.ready, [1] e.suspend and [2] e.resume calls. + Op4 is a mode : 0 (await) 1 (yield) 2 (initial) 3 (final) */ +DEFTREECODE (CO_AWAIT_EXPR, "co_await", tcc_expression, 5) + +/* The co_yield expression is used to support coroutines. + + Op0 is the original expr (for use in diagnostics) + Op2 is the co_await derived from this. */ +DEFTREECODE (CO_YIELD_EXPR, "co_yield", tcc_expression, 2) + +/* The co_return expression is used to support coroutines. + + Op0 is the original expr, can be void (for use in diagnostics) + Op2 is the promise return_xxxx call for Op0. */ + +DEFTREECODE (CO_RETURN_EXPR, "co_return", tcc_expression, 2) + /* Local variables: mode:c diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 48cc44134bc..3d76096b041 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -2703,7 +2703,9 @@ struct GTY(()) lang_decl_fn { unsigned has_dependent_explicit_spec_p : 1; unsigned immediate_fn_p : 1; unsigned maybe_deleted : 1; - unsigned spare : 10; + unsigned coroutine_p : 1; + + unsigned spare : 9; /* 32-bits padding on 64-bit host. */ @@ -4994,6 +4996,13 @@ more_aggr_init_expr_args_p (const aggr_init_expr_arg_iterator *iter) #define QUALIFIED_NAME_IS_TEMPLATE(NODE) \ (TREE_LANG_FLAG_1 (SCOPE_REF_CHECK (NODE))) +/* [coroutines] +*/ + +/* True if NODE is a co-routine FUNCTION_DECL. */ +#define DECL_COROUTINE_P(NODE) \ + (LANG_DECL_FN_CHECK (DECL_COMMON_CHECK (NODE))->coroutine_p) + /* True for an OMP_ATOMIC that has dependent parameters. These are stored as an expr in operand 1, and integer_zero_node or clauses in operand 0. */ #define OMP_ATOMIC_DEPENDENT_P(NODE) \ @@ -7934,6 +7943,14 @@ extern tree cp_ubsan_maybe_instrument_downcast (location_t, tree, tree, tree); extern tree cp_ubsan_maybe_instrument_cast_to_vbase (location_t, tree, tree); extern void cp_ubsan_maybe_initialize_vtbl_ptrs (tree); +/* In coroutines.cc */ +extern tree finish_co_return_stmt (location_t, tree); +extern tree finish_co_await_expr (location_t, tree); +extern tree finish_co_yield_expr (location_t, tree); +extern tree coro_validate_builtin_call (tree, + tsubst_flags_t = tf_warning_or_error); +extern bool morph_fn_to_coro (tree, tree *, tree *); + /* Inline bodies. */ inline tree diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c index 094e32edf58..e58fecc9de7 100644 --- a/gcc/cp/decl.c +++ b/gcc/cp/decl.c @@ -16783,6 +16783,36 @@ add_return_star_this_fixit (gcc_rich_location *richloc, tree fndecl) indent); } +/* This function carries out the subset of finish_function operations needed + to emit the compiler-generated outlined helper functions used by the + coroutines implementation. */ + +static void +emit_coro_helper (tree helper) +{ + /* This is a partial set of the operations done by finish_function() + plus emitting the result. */ + set_cfun (NULL); + current_function_decl = helper; + begin_scope (sk_function_parms, NULL); + store_parm_decls (DECL_ARGUMENTS (helper)); + announce_function (helper); + allocate_struct_function (helper, false); + cfun->language = ggc_cleared_alloc (); + poplevel (1, 0, 1); + maybe_save_function_definition (helper); + /* We must start each function with a clear fold cache. */ + clear_fold_cache (); + cp_fold_function (helper); + DECL_CONTEXT (DECL_RESULT (helper)) = helper; + BLOCK_SUPERCONTEXT (DECL_INITIAL (helper)) = helper; + /* This function has coroutine IFNs that we should handle in middle + end lowering. */ + cfun->coroutine_component = true; + cp_genericize (helper); + expand_or_defer_fn (helper); +} + /* Finish up a function declaration and compile that function all the way to assembler language output. The free the storage for the function definition. INLINE_P is TRUE if we just @@ -16795,6 +16825,10 @@ 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); /* When we get some parse errors, we can end up without a current_function_decl, so cope. */ @@ -16821,6 +16855,25 @@ finish_function (bool inline_p) error_mark_node. */ gcc_assert (DECL_INITIAL (fndecl) == error_mark_node); + if (coro_p) + { + if (!morph_fn_to_coro (fndecl, &resumer, &destroyer)) + { + DECL_SAVED_TREE (fndecl) = pop_stmt_list (DECL_SAVED_TREE (fndecl)); + poplevel (1, 0, 1); + DECL_SAVED_TREE (fndecl) = error_mark_node; + return fndecl; + } + + /* We should handle coroutine IFNs in middle end lowering. */ + cfun->coroutine_component = true; + + if (use_eh_spec_block (fndecl)) + finish_eh_spec_block (TYPE_RAISES_EXCEPTIONS + (TREE_TYPE (fndecl)), + current_eh_spec_block); + } + else /* For a cloned function, we've already got all the code we need; there's no need to add any extra bits. */ if (!DECL_CLONED_FUNCTION_P (fndecl)) @@ -17064,6 +17117,13 @@ finish_function (bool inline_p) && !DECL_OMP_DECLARE_REDUCTION_P (fndecl)) cp_genericize (fndecl); + /* Emit the resumer and destroyer functions now. */ + if (coro_p) + { + emit_coro_helper (resumer); + emit_coro_helper (destroyer); + } + cleanup: /* We're leaving the context of this function, so zap cfun. It's still in DECL_STRUCT_FUNCTION, and we'll restore it in tree_rest_of_compilation. */ diff --git a/gcc/cp/lex.c b/gcc/cp/lex.c index 11b872936a4..37282d56973 100644 --- a/gcc/cp/lex.c +++ b/gcc/cp/lex.c @@ -233,6 +233,8 @@ init_reswords (void) mask |= D_CXX20; if (!flag_concepts) mask |= D_CXX_CONCEPTS; + if (!flag_coroutines) + mask |= D_CXX_COROUTINES; if (!flag_tm) mask |= D_TRANSMEM; if (!flag_char8_t) diff --git a/gcc/cp/operators.def b/gcc/cp/operators.def index c0a659381ba..d2395355af3 100644 --- a/gcc/cp/operators.def +++ b/gcc/cp/operators.def @@ -87,6 +87,7 @@ DEF_OPERATOR ("++", PREINCREMENT_EXPR, "pp", OVL_OP_FLAG_UNARY) DEF_OPERATOR ("--", PREDECREMENT_EXPR, "mm", OVL_OP_FLAG_UNARY) DEF_OPERATOR ("->", COMPONENT_REF, "pt", OVL_OP_FLAG_UNARY) DEF_OPERATOR ("sizeof", SIZEOF_EXPR, "sz", OVL_OP_FLAG_UNARY) +DEF_OPERATOR ("co_await", CO_AWAIT_EXPR, "aw", OVL_OP_FLAG_UNARY) /* These are extensions. */ DEF_OPERATOR ("alignof", ALIGNOF_EXPR, "az", OVL_OP_FLAG_UNARY) diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index 728474dc545..75e32fcebcb 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -177,7 +177,9 @@ enum required_token { RT_CLASS_TYPENAME_TEMPLATE, /* class, typename, or template */ RT_TRANSACTION_ATOMIC, /* __transaction_atomic */ RT_TRANSACTION_RELAXED, /* __transaction_relaxed */ - RT_TRANSACTION_CANCEL /* __transaction_cancel */ + RT_TRANSACTION_CANCEL, /* __transaction_cancel */ + + RT_CO_YIELD /* co_yield */ }; /* RAII wrapper for parser->in_type_id_in_expr_p, setting it on creation and @@ -2471,6 +2473,12 @@ static void cp_parser_function_transaction static tree cp_parser_transaction_cancel (cp_parser *); +/* Coroutine extensions. */ + +static tree cp_parser_yield_expression + (cp_parser *); + + enum pragma_context { pragma_external, pragma_member, @@ -8112,6 +8120,7 @@ cp_parser_pseudo_destructor_name (cp_parser* parser, postfix-expression ++ cast-expression -- cast-expression + await-expression unary-operator cast-expression sizeof unary-expression sizeof ( type-id ) @@ -8326,6 +8335,22 @@ cp_parser_unary_expression (cp_parser *parser, cp_id_kind * pidk, noexcept_loc); } + case RID_CO_AWAIT: + { + tree expr; + location_t kw_loc = token->location; + + /* Consume the `co_await' token. */ + cp_lexer_consume_token (parser->lexer); + /* Parse its cast-expression. */ + expr = cp_parser_simple_cast_expression (parser); + if (expr == error_mark_node) + return error_mark_node; + + /* Handle [expr.await]. */ + return cp_expr (finish_co_await_expr (kw_loc, expr)); + } + default: break; } @@ -9757,6 +9782,7 @@ cp_parser_question_colon_clause (cp_parser* parser, cp_expr logical_or_expr) conditional-expression logical-or-expression assignment-operator assignment_expression throw-expression + yield-expression CAST_P is true if this expression is the target of a cast. DECLTYPE_P is true if this expression is the operand of decltype. @@ -9773,6 +9799,10 @@ cp_parser_assignment_expression (cp_parser* parser, cp_id_kind * pidk, a throw-expression. */ if (cp_lexer_next_token_is_keyword (parser->lexer, RID_THROW)) expr = cp_parser_throw_expression (parser); + /* If the next token is the `co_yield' keyword, then we're looking at + a yield-expression. */ + else if (cp_lexer_next_token_is_keyword (parser->lexer, RID_CO_YIELD)) + expr = cp_parser_yield_expression (parser); /* Otherwise, it must be that we are looking at a logical-or-expression. */ else @@ -11271,6 +11301,7 @@ cp_parser_statement (cp_parser* parser, tree in_statement_expr, case RID_BREAK: case RID_CONTINUE: case RID_RETURN: + case RID_CO_RETURN: case RID_GOTO: std_attrs = process_stmt_hotness_attribute (std_attrs, attrs_loc); statement = cp_parser_jump_statement (parser); @@ -12915,6 +12946,7 @@ cp_parser_init_statement (cp_parser *parser, tree *decl) continue ; return expression [opt] ; return braced-init-list ; + coroutine-return-statement; goto identifier ; GNU extension: @@ -12985,6 +13017,7 @@ cp_parser_jump_statement (cp_parser* parser) cp_parser_require (parser, CPP_SEMICOLON, RT_SEMICOLON); break; + case RID_CO_RETURN: case RID_RETURN: { tree expr; @@ -13002,8 +13035,11 @@ cp_parser_jump_statement (cp_parser* parser) /* If the next token is a `;', then there is no expression. */ expr = NULL_TREE; - /* Build the return-statement. */ - if (FNDECL_USED_AUTO (current_function_decl) && in_discarded_stmt) + /* Build the return-statement, check co-return first, since type + deduction is not valid there. */ + if (keyword == RID_CO_RETURN) + statement = finish_co_return_stmt (token->location, expr); + else if (FNDECL_USED_AUTO (current_function_decl) && in_discarded_stmt) /* Don't deduce from a discarded return statement. */; else statement = finish_return_stmt (expr); @@ -15383,22 +15419,25 @@ cp_parser_operator (cp_parser* parser, location_t start_loc) { case CPP_KEYWORD: { - /* The keyword should be either `new' or `delete'. */ + /* The keyword should be either `new', `delete' or `co_await'. */ if (token->keyword == RID_NEW) op = NEW_EXPR; else if (token->keyword == RID_DELETE) op = DELETE_EXPR; + else if (token->keyword == RID_CO_AWAIT) + op = CO_AWAIT_EXPR; else break; - /* Consume the `new' or `delete' token. */ + /* Consume the `new', `delete' or co_await token. */ end_loc = cp_lexer_consume_token (parser->lexer)->location; /* Peek at the next token. */ token = cp_lexer_peek_token (parser->lexer); /* If it's a `[' token then this is the array variant of the operator. */ - if (token->type == CPP_OPEN_SQUARE) + if (token->type == CPP_OPEN_SQUARE + && op != CO_AWAIT_EXPR) { /* Consume the `[' token. */ cp_lexer_consume_token (parser->lexer); @@ -26085,6 +26124,41 @@ cp_parser_throw_expression (cp_parser* parser) return expression; } +/* Parse a yield-expression. + + yield-expression: + co_yield assignment-expression + co_yield braced-init-list + + Returns a CO_YIELD_EXPR representing the yield-expression. */ + +static tree +cp_parser_yield_expression (cp_parser* parser) +{ + tree expr; + + cp_token *token = cp_lexer_peek_token (parser->lexer); + location_t kw_loc = token->location; /* Save for later. */ + + cp_parser_require_keyword (parser, RID_CO_YIELD, RT_CO_YIELD); + + if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE)) + { + bool expr_non_constant_p; + cp_lexer_set_source_position (parser->lexer); + /* ??? : probably a moot point? */ + maybe_warn_cpp0x (CPP0X_INITIALIZER_LISTS); + expr = cp_parser_braced_list (parser, &expr_non_constant_p); + } + else + expr = cp_parser_assignment_expression (parser); + + if (expr == error_mark_node) + return expr; + + return finish_co_yield_expr (kw_loc, expr); +} + /* GNU Extensions */ /* Parse an (optional) asm-specification. @@ -30337,6 +30411,9 @@ cp_parser_required_error (cp_parser *parser, case RT_TRANSACTION_RELAXED: gmsgid = G_("expected %<__transaction_relaxed%>"); break; + case RT_CO_YIELD: + gmsgid = G_("expected %"); + break; default: break; } diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index 1b3d07b1a52..cad97514cdc 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -16814,6 +16814,11 @@ tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl) to the containing function, inlined copy or so. */ return t; + case CO_AWAIT_EXPR: + return tsubst_expr (t, args, complain, in_decl, + /*integral_constant_expression_p=*/false); + break; + default: /* We shouldn't get here, but keep going if !flag_checking. */ if (flag_checking) @@ -17680,6 +17685,22 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl, finish_return_stmt (RECUR (TREE_OPERAND (t, 0))); break; + case CO_RETURN_EXPR: + finish_co_return_stmt (input_location, RECUR (TREE_OPERAND (t, 0))); + break; + + case CO_YIELD_EXPR: + stmt = finish_co_yield_expr (input_location, + RECUR (TREE_OPERAND (t, 0))); + RETURN (stmt); + break; + + case CO_AWAIT_EXPR: + stmt = finish_co_await_expr (input_location, + RECUR (TREE_OPERAND (t, 0))); + RETURN (stmt); + break; + case EXPR_STMT: tmp = RECUR (EXPR_STMT_EXPR (t)); if (EXPR_STMT_STMT_EXPR_RESULT (t)) diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c index d59962c5899..21f733af486 100644 --- a/gcc/cp/tree.c +++ b/gcc/cp/tree.c @@ -5068,6 +5068,37 @@ cp_walk_subtrees (tree *tp, int *walk_subtrees_p, walk_tree_fn func, WALK_SUBTREE (TREE_VALUE (cap)); break; + case CO_YIELD_EXPR: + if (TREE_OPERAND (*tp, 1)) + /* Operand 1 is the tree for the relevant co_await which has any + interesting sub-trees. */ + WALK_SUBTREE (TREE_OPERAND (*tp, 1)); + break; + + case CO_AWAIT_EXPR: + if (TREE_OPERAND (*tp, 1)) + /* Operand 1 is frame variable. */ + WALK_SUBTREE (TREE_OPERAND (*tp, 1)); + if (TREE_OPERAND (*tp, 2)) + /* Operand 2 has the initialiser, and we need to walk any subtrees + there. */ + WALK_SUBTREE (TREE_OPERAND (*tp, 2)); + break; + + case CO_RETURN_EXPR: + if (TREE_OPERAND (*tp, 0)) + { + if (VOID_TYPE_P (TREE_OPERAND (*tp, 0))) + /* For void expressions, operand 1 is a trivial call, and any + interesting subtrees will be part of operand 0. */ + WALK_SUBTREE (TREE_OPERAND (*tp, 0)); + else if (TREE_OPERAND (*tp, 1)) + /* Interesting sub-trees will be in the return_value () call + arguments. */ + WALK_SUBTREE (TREE_OPERAND (*tp, 1)); + } + break; + default: return NULL_TREE; } diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 355bf1b247a..bbd03f87c67 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -2628,6 +2628,10 @@ of a loop too many expressions need to be evaluated, the resulting constexpr evaluation might take too long. The default is 33554432 (1<<25). +@item -fcoroutines +@opindex fcoroutines +Enable support for the C++ coroutines extension (experimental). + @item -fno-elide-constructors @opindex fno-elide-constructors @opindex felide-constructors diff --git a/gcc/function.h b/gcc/function.h index 496d3f728c8..1ee8ed3de53 100644 --- a/gcc/function.h +++ b/gcc/function.h @@ -418,6 +418,9 @@ struct GTY(()) function { /* Set when the function was compiled with generation of debug (begin stmt, inline entry, ...) markers enabled. */ unsigned int debug_nonbind_markers : 1; + + /* Set if this is a coroutine-related function. */ + unsigned int coroutine_component : 1; }; /* Add the decl D to the local_decls list of FUN. */ diff --git a/gcc/internal-fn.c b/gcc/internal-fn.c index ceac2df9ced..52d1638917a 100644 --- a/gcc/internal-fn.c +++ b/gcc/internal-fn.c @@ -2884,6 +2884,32 @@ expand_NOP (internal_fn, gcall *) /* Nothing. But it shouldn't really prevail. */ } +/* Coroutines, all should have been processed at this stage. */ + +static void +expand_CO_FRAME (internal_fn, gcall *) +{ + gcc_unreachable (); +} + +static void +expand_CO_YIELD (internal_fn, gcall *) +{ + gcc_unreachable (); +} + +static void +expand_CO_SUSPN (internal_fn, gcall *) +{ + gcc_unreachable (); +} + +static void +expand_CO_ACTOR (internal_fn, gcall *) +{ + gcc_unreachable (); +} + /* Expand a call to FN using the operands in STMT. FN has a single output operand and NARGS input operands. */ diff --git a/gcc/internal-fn.def b/gcc/internal-fn.def index ed11b93b671..1d190d492ff 100644 --- a/gcc/internal-fn.def +++ b/gcc/internal-fn.def @@ -366,6 +366,12 @@ DEF_INTERNAL_FN (LAUNDER, ECF_LEAF | ECF_NOTHROW | ECF_NOVOPS, NULL) /* Divmod function. */ DEF_INTERNAL_FN (DIVMOD, ECF_CONST | ECF_LEAF, NULL) +/* For coroutines. */ +DEF_INTERNAL_FN (CO_ACTOR, ECF_NOTHROW | ECF_LEAF, NULL) +DEF_INTERNAL_FN (CO_YIELD, ECF_NOTHROW, NULL) +DEF_INTERNAL_FN (CO_SUSPN, ECF_NOTHROW, NULL) +DEF_INTERNAL_FN (CO_FRAME, ECF_PURE | ECF_NOTHROW | ECF_LEAF, NULL) + /* A NOP function with arbitrary arguments and return value. */ DEF_INTERNAL_FN (NOP, ECF_CONST | ECF_LEAF | ECF_NOTHROW, NULL) diff --git a/gcc/passes.def b/gcc/passes.def index 0106c8d5fd0..2bf2cb78fc5 100644 --- a/gcc/passes.def +++ b/gcc/passes.def @@ -39,8 +39,10 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_lower_tm); NEXT_PASS (pass_refactor_eh); NEXT_PASS (pass_lower_eh); + NEXT_PASS (pass_coroutine_lower_builtins); NEXT_PASS (pass_build_cfg); NEXT_PASS (pass_warn_function_return); + NEXT_PASS (pass_coroutine_early_expand_ifns); NEXT_PASS (pass_expand_omp); NEXT_PASS (pass_warn_printf); NEXT_PASS (pass_walloca, /*strict_mode_p=*/true); diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 9eacb7f568f..14273ae3bd6 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,123 @@ +2020-01-18 Iain Sandoe + + * g++.dg/coroutines/co-await-syntax-00-needs-expr.C: New test. + * g++.dg/coroutines/co-await-syntax-01-outside-fn.C: New test. + * g++.dg/coroutines/co-await-syntax-02-outside-fn.C: New test. + * g++.dg/coroutines/co-await-syntax-03-auto.C: New test. + * g++.dg/coroutines/co-await-syntax-04-ctor-dtor.C: New test. + * g++.dg/coroutines/co-await-syntax-05-constexpr.C: New test. + * g++.dg/coroutines/co-await-syntax-06-main.C: New test. + * g++.dg/coroutines/co-await-syntax-07-varargs.C: New test. + * g++.dg/coroutines/co-await-syntax-08-lambda-auto.C: New test. + * g++.dg/coroutines/co-return-syntax-01-outside-fn.C: New test. + * g++.dg/coroutines/co-return-syntax-02-outside-fn.C: New test. + * g++.dg/coroutines/co-return-syntax-03-auto.C: New test. + * g++.dg/coroutines/co-return-syntax-04-ctor-dtor.C: New test. + * g++.dg/coroutines/co-return-syntax-05-constexpr-fn.C: New test. + * g++.dg/coroutines/co-return-syntax-06-main.C: New test. + * g++.dg/coroutines/co-return-syntax-07-vararg.C: New test. + * g++.dg/coroutines/co-return-syntax-08-bad-return.C: New test. + * g++.dg/coroutines/co-return-syntax-09-lambda-auto.C: New test. + * g++.dg/coroutines/co-yield-syntax-00-needs-expr.C: New test. + * g++.dg/coroutines/co-yield-syntax-01-outside-fn.C: New test. + * g++.dg/coroutines/co-yield-syntax-02-outside-fn.C: New test. + * g++.dg/coroutines/co-yield-syntax-03-auto.C: New test. + * g++.dg/coroutines/co-yield-syntax-04-ctor-dtor.C: New test. + * g++.dg/coroutines/co-yield-syntax-05-constexpr.C: New test. + * g++.dg/coroutines/co-yield-syntax-06-main.C: New test. + * g++.dg/coroutines/co-yield-syntax-07-varargs.C: New test. + * g++.dg/coroutines/co-yield-syntax-08-needs-expr.C: New test. + * g++.dg/coroutines/co-yield-syntax-09-lambda-auto.C: New test. + * g++.dg/coroutines/coro-builtins.C: New test. + * g++.dg/coroutines/coro-missing-gro.C: New test. + * g++.dg/coroutines/coro-missing-promise-yield.C: New test. + * g++.dg/coroutines/coro-missing-ret-value.C: New test. + * g++.dg/coroutines/coro-missing-ret-void.C: New test. + * g++.dg/coroutines/coro-missing-ueh-1.C: New test. + * g++.dg/coroutines/coro-missing-ueh-2.C: New test. + * g++.dg/coroutines/coro-missing-ueh-3.C: New test. + * g++.dg/coroutines/coro-missing-ueh.h: New test. + * g++.dg/coroutines/coro-pre-proc.C: New test. + * g++.dg/coroutines/coro.h: New file. + * g++.dg/coroutines/coro1-ret-int-yield-int.h: New file. + * g++.dg/coroutines/coroutines.exp: New file. + * g++.dg/coroutines/torture/alloc-00-gro-on-alloc-fail.C: New test. + * g++.dg/coroutines/torture/alloc-01-overload-newdel.C: New test. + * g++.dg/coroutines/torture/call-00-co-aw-arg.C: New test. + * g++.dg/coroutines/torture/call-01-multiple-co-aw.C: New test. + * g++.dg/coroutines/torture/call-02-temp-co-aw.C: New test. + * g++.dg/coroutines/torture/call-03-temp-ref-co-aw.C: New test. + * g++.dg/coroutines/torture/class-00-co-ret.C: New test. + * g++.dg/coroutines/torture/class-01-co-ret-parm.C: New test. + * g++.dg/coroutines/torture/class-02-templ-parm.C: New test. + * g++.dg/coroutines/torture/class-03-operator-templ-parm.C: New test. + * g++.dg/coroutines/torture/class-04-lambda-1.C: New test. + * g++.dg/coroutines/torture/class-05-lambda-capture-copy-local.C: New test. + * g++.dg/coroutines/torture/class-06-lambda-capture-ref.C: New test. + * g++.dg/coroutines/torture/co-await-00-trivial.C: New test. + * g++.dg/coroutines/torture/co-await-01-with-value.C: New test. + * g++.dg/coroutines/torture/co-await-02-xform.C: New test. + * g++.dg/coroutines/torture/co-await-03-rhs-op.C: New test. + * g++.dg/coroutines/torture/co-await-04-control-flow.C: New test. + * g++.dg/coroutines/torture/co-await-05-loop.C: New test. + * g++.dg/coroutines/torture/co-await-06-ovl.C: New test. + * g++.dg/coroutines/torture/co-await-07-tmpl.C: New test. + * g++.dg/coroutines/torture/co-await-08-cascade.C: New test. + * g++.dg/coroutines/torture/co-await-09-pair.C: New test. + * g++.dg/coroutines/torture/co-await-10-template-fn-arg.C: New test. + * g++.dg/coroutines/torture/co-await-11-forwarding.C: New test. + * g++.dg/coroutines/torture/co-await-12-operator-2.C: New test. + * g++.dg/coroutines/torture/co-await-13-return-ref.C: New test. + * g++.dg/coroutines/torture/co-ret-00-void-return-is-ready.C: New test. + * g++.dg/coroutines/torture/co-ret-01-void-return-is-suspend.C: New test. + * g++.dg/coroutines/torture/co-ret-03-different-GRO-type.C: New test. + * g++.dg/coroutines/torture/co-ret-04-GRO-nontriv.C: New test. + * g++.dg/coroutines/torture/co-ret-05-return-value.C: New test. + * g++.dg/coroutines/torture/co-ret-06-template-promise-val-1.C: New test. + * g++.dg/coroutines/torture/co-ret-07-void-cast-expr.C: New test. + * g++.dg/coroutines/torture/co-ret-08-template-cast-ret.C: New test. + * g++.dg/coroutines/torture/co-ret-09-bool-await-susp.C: New test. + * g++.dg/coroutines/torture/co-ret-10-expression-evaluates-once.C: New test. + * g++.dg/coroutines/torture/co-ret-11-co-ret-co-await.C: New test. + * g++.dg/coroutines/torture/co-ret-12-co-ret-fun-co-await.C: New test. + * g++.dg/coroutines/torture/co-ret-13-template-2.C: New test. + * g++.dg/coroutines/torture/co-ret-14-template-3.C: New test. + * g++.dg/coroutines/torture/co-yield-00-triv.C: New test. + * g++.dg/coroutines/torture/co-yield-01-multi.C: New test. + * g++.dg/coroutines/torture/co-yield-02-loop.C: New test. + * g++.dg/coroutines/torture/co-yield-03-tmpl.C: New test. + * g++.dg/coroutines/torture/co-yield-04-complex-local-state.C: New test. + * g++.dg/coroutines/torture/co-yield-05-co-aw.C: New test. + * g++.dg/coroutines/torture/co-yield-06-fun-parm.C: New test. + * g++.dg/coroutines/torture/co-yield-07-template-fn-param.C: New test. + * g++.dg/coroutines/torture/co-yield-08-more-refs.C: New test. + * g++.dg/coroutines/torture/co-yield-09-more-templ-refs.C: New test. + * g++.dg/coroutines/torture/coro-torture.exp: New file. + * g++.dg/coroutines/torture/exceptions-test-0.C: New test. + * g++.dg/coroutines/torture/func-params-00.C: New test. + * g++.dg/coroutines/torture/func-params-01.C: New test. + * g++.dg/coroutines/torture/func-params-02.C: New test. + * g++.dg/coroutines/torture/func-params-03.C: New test. + * g++.dg/coroutines/torture/func-params-04.C: New test. + * g++.dg/coroutines/torture/func-params-05.C: New test. + * g++.dg/coroutines/torture/func-params-06.C: New test. + * g++.dg/coroutines/torture/lambda-00-co-ret.C: New test. + * g++.dg/coroutines/torture/lambda-01-co-ret-parm.C: New test. + * g++.dg/coroutines/torture/lambda-02-co-yield-values.C: New test. + * g++.dg/coroutines/torture/lambda-03-auto-parm-1.C: New test. + * g++.dg/coroutines/torture/lambda-04-templ-parm.C: New test. + * g++.dg/coroutines/torture/lambda-05-capture-copy-local.C: New test. + * g++.dg/coroutines/torture/lambda-06-multi-capture.C: New test. + * g++.dg/coroutines/torture/lambda-07-multi-yield.C: New test. + * g++.dg/coroutines/torture/lambda-08-co-ret-parm-ref.C: New test. + * g++.dg/coroutines/torture/local-var-0.C: New test. + * g++.dg/coroutines/torture/local-var-1.C: New test. + * g++.dg/coroutines/torture/local-var-2.C: New test. + * g++.dg/coroutines/torture/local-var-3.C: New test. + * g++.dg/coroutines/torture/local-var-4.C: New test. + * g++.dg/coroutines/torture/mid-suspend-destruction-0.C: New test. + * g++.dg/coroutines/torture/pr92933.C: New test. + 2020-01-17 Jerry DeLisle PR libfortran/93234 diff --git a/gcc/testsuite/g++.dg/coroutines/co-await-syntax-00-needs-expr.C b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-00-needs-expr.C new file mode 100644 index 00000000000..d068c3d19af --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-00-needs-expr.C @@ -0,0 +1,7 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +void bar () { + co_await; // { dg-error "expected primary-expression before" } +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-await-syntax-01-outside-fn.C b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-01-outside-fn.C new file mode 100644 index 00000000000..484859c7062 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-01-outside-fn.C @@ -0,0 +1,5 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +int x = co_await coro::suspend_always{}; // { dg-error {'co_await' cannot be used outside a function} } diff --git a/gcc/testsuite/g++.dg/coroutines/co-await-syntax-02-outside-fn.C b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-02-outside-fn.C new file mode 100644 index 00000000000..4ce5c2e04a0 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-02-outside-fn.C @@ -0,0 +1,5 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +auto f (int x = co_await coro::suspend_always{}); // { dg-error {'co_await' cannot be used outside a function} } diff --git a/gcc/testsuite/g++.dg/coroutines/co-await-syntax-03-auto.C b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-03-auto.C new file mode 100644 index 00000000000..7f4ed9afef5 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-03-auto.C @@ -0,0 +1,16 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +extern struct awaitable *aw (); + +auto bar () { + int x = 1 + co_await *aw(); // { dg-error "cannot be used in a function with a deduced return type" } + + return x; +} + +int main () { + bar (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-await-syntax-04-ctor-dtor.C b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-04-ctor-dtor.C new file mode 100644 index 00000000000..ac0ba2e54f8 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-04-ctor-dtor.C @@ -0,0 +1,8 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +struct Foo { + Foo () { co_await coro::suspend_always{}; } // { dg-error "cannot be used in a constructor" } + ~Foo () { co_await coro::suspend_always{}; } // { dg-error "cannot be used in a destructor" } +}; diff --git a/gcc/testsuite/g++.dg/coroutines/co-await-syntax-05-constexpr.C b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-05-constexpr.C new file mode 100644 index 00000000000..73a0b1499d4 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-05-constexpr.C @@ -0,0 +1,12 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +constexpr int bar () { + co_await coro::suspend_always{}; // { dg-error "cannot be used in a .constexpr. function" } + return 42; /* Suppress the "no return" error. */ +} + +int main () { + return bar (); +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-await-syntax-06-main.C b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-06-main.C new file mode 100644 index 00000000000..ab520baaff1 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-06-main.C @@ -0,0 +1,7 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +int main (int ac, char *av[]) { + co_await coro::suspend_always{}; // { dg-error "cannot be used in the .main. function" } +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-await-syntax-07-varargs.C b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-07-varargs.C new file mode 100644 index 00000000000..4e41dd3be4a --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-07-varargs.C @@ -0,0 +1,14 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +int +bar (int x, ...) +{ + co_await coro::suspend_always{}; // { dg-error "cannot be used in a varargs function" } +} + +int main (int ac, char *av[]) { + bar (5, ac); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-await-syntax-08-lambda-auto.C b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-08-lambda-auto.C new file mode 100644 index 00000000000..61db5feed32 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-await-syntax-08-lambda-auto.C @@ -0,0 +1,19 @@ +// { dg-additional-options "-fsyntax-only -w" } + +// Check that we decline return type deduction for lambda coroutines. + +#include "coro.h" + +// boiler-plate for tests of codegen +#include "coro1-ret-int-yield-int.h" + +int main () +{ + /* Attempt to deduce the return type for a lambda coroutine. */ + auto f = []() + { + co_await coro::suspend_always{}; // { dg-error "cannot be used in a function with a deduced return type" } + }; + + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-01-outside-fn.C b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-01-outside-fn.C new file mode 100644 index 00000000000..3fcd8dd104d --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-01-outside-fn.C @@ -0,0 +1,6 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +co_return; // { dg-error {expected unqualified-id before 'co_return'} } + diff --git a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-02-outside-fn.C b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-02-outside-fn.C new file mode 100644 index 00000000000..cda36eb2a3d --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-02-outside-fn.C @@ -0,0 +1,5 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +auto f (co_return); // { dg-error {expected primary-expression before 'co_return'} } diff --git a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-03-auto.C b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-03-auto.C new file mode 100644 index 00000000000..93a04dc459a --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-03-auto.C @@ -0,0 +1,12 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +auto bar () { + co_return 5; // { dg-error "cannot be used in a function with a deduced return type" } +} + +int main () { + bar (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-04-ctor-dtor.C b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-04-ctor-dtor.C new file mode 100644 index 00000000000..9396432e8bf --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-04-ctor-dtor.C @@ -0,0 +1,8 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +struct Foo { + Foo () { co_return; } // { dg-error "cannot be used in a constructor" } + ~Foo () { co_return 5; } // { dg-error "cannot be used in a destructor" } +}; diff --git a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-05-constexpr-fn.C b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-05-constexpr-fn.C new file mode 100644 index 00000000000..69b109fb604 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-05-constexpr-fn.C @@ -0,0 +1,12 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +constexpr int bar () { + co_return 5; // { dg-error "cannot be used in a .constexpr. function" } + return 42; /* Suppress the "no return" error. */ +} + +int main () { + return bar (); +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-06-main.C b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-06-main.C new file mode 100644 index 00000000000..40d7e4e3623 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-06-main.C @@ -0,0 +1,7 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +int main (int ac, char *av[]) { + co_return 0; // { dg-error "cannot be used in the .main. function" } +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-07-vararg.C b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-07-vararg.C new file mode 100644 index 00000000000..0aea17a1db8 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-07-vararg.C @@ -0,0 +1,14 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +int +bar (int x, ...) +{ + co_return 1; // { dg-error "cannot be used in a varargs function" } +} + +int main (int ac, char *av[]) { + bar (5, ac); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-08-bad-return.C b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-08-bad-return.C new file mode 100644 index 00000000000..4bfa41cd4a9 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-08-bad-return.C @@ -0,0 +1,43 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +struct Coro { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + Coro () : handle(0) {} + Coro (handle_type _handle) : handle(_handle) {} + Coro (Coro &&s) : handle(s.handle) { s.handle = nullptr; } + Coro &operator = (Coro &&s) { + handle = s.handle; + s.handle = nullptr; + return *this; + } + Coro (const Coro &) = delete; + ~Coro() { + if ( handle ) + handle.destroy(); + } + struct promise_type { + promise_type() {} + ~promise_type() {} + Coro get_return_object () { return Coro (handle_type::from_promise (*this)); } + auto initial_suspend () { return coro::suspend_always{}; } + auto final_suspend () { return coro::suspend_always{}; } + void return_void () { } + void unhandled_exception() { } + }; +}; + +extern int x; + +// Diagnose disallowed "return" in coroutine. +Coro +bar () // { dg-error {a 'return' statement is not allowed} } +{ + if (x) + return Coro(); + else + co_return; +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-09-lambda-auto.C b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-09-lambda-auto.C new file mode 100644 index 00000000000..8fe52361ba2 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-09-lambda-auto.C @@ -0,0 +1,19 @@ +// { dg-additional-options "-fsyntax-only -w" } + +// Check that we decline return type deduction for lambda coroutines. + +#include "coro.h" + +// boiler-plate for tests of codegen +#include "coro1-ret-int-yield-int.h" + +int main () +{ + /* Attempt to deduce the return type for a lambda coroutine. */ + auto f = []() + { + co_return 42; // { dg-error "cannot be used in a function with a deduced return type" } + }; + + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-00-needs-expr.C b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-00-needs-expr.C new file mode 100644 index 00000000000..547f1a31c0c --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-00-needs-expr.C @@ -0,0 +1,7 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +void foo () { + co_yield; // { dg-error "expected primary-expression before" } +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-01-outside-fn.C b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-01-outside-fn.C new file mode 100644 index 00000000000..30db0e963b0 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-01-outside-fn.C @@ -0,0 +1,6 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +auto f (int x = co_yield 5); // { dg-error {'co_yield' cannot be used outside a function} } + diff --git a/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-02-outside-fn.C b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-02-outside-fn.C new file mode 100644 index 00000000000..71e119fbef3 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-02-outside-fn.C @@ -0,0 +1,6 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +int a[] = { co_yield 21 }; // { dg-error {'co_yield' cannot be used outside a function} } + diff --git a/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-03-auto.C b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-03-auto.C new file mode 100644 index 00000000000..808a07f5e14 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-03-auto.C @@ -0,0 +1,12 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +auto bar () { + co_yield 5; // { dg-error "cannot be used in a function with a deduced return type" } +} + +int main () { + bar (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-04-ctor-dtor.C b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-04-ctor-dtor.C new file mode 100644 index 00000000000..cc46e01d778 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-04-ctor-dtor.C @@ -0,0 +1,8 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +struct Foo { + Foo () { co_yield 4; } // { dg-error "cannot be used in a constructor" } + ~Foo () { co_yield 4; } // { dg-error "cannot be used in a destructor" } +}; diff --git a/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-05-constexpr.C b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-05-constexpr.C new file mode 100644 index 00000000000..39ef19c63b9 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-05-constexpr.C @@ -0,0 +1,12 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +constexpr int bar () { + co_yield 5; // { dg-error "cannot be used in a .constexpr. function" } + return 42; /* Suppress the "no return" error. */ +} + +int main () { + return bar (); +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-06-main.C b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-06-main.C new file mode 100644 index 00000000000..dcc3dbce756 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-06-main.C @@ -0,0 +1,7 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +int main (int ac, char *av[]) { + co_yield 0; // { dg-error "cannot be used in the .main. function" } +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-07-varargs.C b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-07-varargs.C new file mode 100644 index 00000000000..f0b568335e4 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-07-varargs.C @@ -0,0 +1,14 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +int +bar (int x, ...) +{ + co_yield 1; // { dg-error "cannot be used in a varargs function" } +} + +int main (int ac, char *av[]) { + bar (5, ac); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-08-needs-expr.C b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-08-needs-expr.C new file mode 100644 index 00000000000..86969f781e4 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-08-needs-expr.C @@ -0,0 +1,37 @@ +// { dg-additional-options "-fsyntax-only -w" } + +// Check syntax for missing expr in a coroutine context. + +#include "coro.h" + +struct DummyYield { + coro::coroutine_handle<> handle; + DummyYield () : handle (nullptr) {} + DummyYield (coro::coroutine_handle<> handle) : handle (handle) {} + struct dummy_yield { + coro::suspend_never initial_suspend() { return {}; } + coro::suspend_never final_suspend() { return {}; } + DummyYield get_return_object() { + return DummyYield (coro::coroutine_handle::from_promise (*this)); + } + void yield_value (int v) {} + void return_value (int v) {} + void unhandled_exception() { /*std::terminate();*/ }; + }; +}; + +template<> struct coro::coroutine_traits { + using promise_type = DummyYield::dummy_yield; +}; + +DummyYield +bar () +{ + co_yield; // { dg-error {expected primary-expression before} } + co_return 0; +} + +int main (int ac, char *av[]) { + DummyYield x = bar (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-09-lambda-auto.C b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-09-lambda-auto.C new file mode 100644 index 00000000000..5190face000 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/co-yield-syntax-09-lambda-auto.C @@ -0,0 +1,19 @@ +// { dg-additional-options "-fsyntax-only -w" } + +// Check that we decline return type deduction for lambda coroutines. + +#include "coro.h" + +// boiler-plate for tests of codegen +#include "coro1-ret-int-yield-int.h" + +int main () +{ + /* Attempt to deduce the return type for a lambda coroutine. */ + auto f = []() + { + co_yield 42; // { dg-error "cannot be used in a function with a deduced return type" } + }; + + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/coro-builtins.C b/gcc/testsuite/g++.dg/coroutines/coro-builtins.C new file mode 100644 index 00000000000..d7c48833844 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro-builtins.C @@ -0,0 +1,17 @@ +// { dg-additional-options "-fsyntax-only " } + +typedef __SIZE_TYPE__ size_t; + +int main () +{ + void *co_h; + void *promise; + const size_t co_align = 16; + + bool d = __builtin_coro_done (co_h); + __builtin_coro_resume (co_h); + __builtin_coro_destroy (co_h); + promise = __builtin_coro_promise (co_h, co_align, true); + + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/coro-missing-gro.C b/gcc/testsuite/g++.dg/coroutines/coro-missing-gro.C new file mode 100644 index 00000000000..fb02e9d5801 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro-missing-gro.C @@ -0,0 +1,32 @@ +// { dg-additional-options "-fsyntax-only -w" } + +// Diagose missing get_return_object() in the promise type. + +#include "coro.h" + +struct MissingGRO { + coro::coroutine_handle<> handle; + MissingGRO () : handle (nullptr) {} + MissingGRO (coro::coroutine_handle<> handle) : handle (handle) {} + struct missing_gro { + coro::suspend_never initial_suspend() { return {}; } + coro::suspend_never final_suspend() { return {}; } + void return_void () {} + void unhandled_exception() { /*std::terminate();*/ }; + }; +}; + +template<> struct coro::coroutine_traits { + using promise_type = MissingGRO::missing_gro; +}; + +MissingGRO +bar () // { dg-error {no member named 'get_return_object' in} } +{ + co_return; +} + +int main (int ac, char *av[]) { + MissingGRO x = bar (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/coro-missing-promise-yield.C b/gcc/testsuite/g++.dg/coroutines/coro-missing-promise-yield.C new file mode 100644 index 00000000000..d489c3953ac --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro-missing-promise-yield.C @@ -0,0 +1,33 @@ +// { dg-additional-options "-fsyntax-only -w" } +#include "coro.h" + +struct MissingPromiseYield { + coro::coroutine_handle<> handle; + MissingPromiseYield () : handle (nullptr) {} + MissingPromiseYield (coro::coroutine_handle<> handle) : handle (handle) {} + struct missing_yield { + coro::suspend_never initial_suspend() { return {}; } + coro::suspend_never final_suspend() { return {}; } + MissingPromiseYield get_return_object() { + return MissingPromiseYield (coro::coroutine_handle::from_promise (*this)); + } + void return_value (int v) {} + void unhandled_exception() { /*std::terminate();*/ }; + }; +}; + +template<> struct coro::coroutine_traits { + using promise_type = MissingPromiseYield::missing_yield; +}; + +MissingPromiseYield +bar () +{ + co_yield 22; // { dg-error {no member named 'yield_value' in} } + co_return 0; +} + +int main (int ac, char *av[]) { + MissingPromiseYield x = bar (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/coro-missing-ret-value.C b/gcc/testsuite/g++.dg/coroutines/coro-missing-ret-value.C new file mode 100644 index 00000000000..f238c4b9a35 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro-missing-ret-value.C @@ -0,0 +1,34 @@ +// { dg-additional-options "-fsyntax-only -w" } + +// Diagose missing return_value() in the promise type. + +#include "coro.h" + +struct MissingRetValue { + coro::coroutine_handle<> handle; + MissingRetValue () : handle (nullptr) {} + MissingRetValue (coro::coroutine_handle<> handle) : handle (handle) {} + struct missing_retvoid { + coro::suspend_never initial_suspend() { return {}; } + coro::suspend_never final_suspend() { return {}; } + MissingRetValue get_return_object() { + return MissingRetValue (coro::coroutine_handle::from_promise (*this)); + } + void unhandled_exception() { /*std::terminate();*/ }; + }; +}; + +template<> struct coro::coroutine_traits { + using promise_type = MissingRetValue::missing_retvoid; +}; + +MissingRetValue +bar () +{ + co_return 6174; // { dg-error {no member named 'return_value' in} } +} + +int main (int ac, char *av[]) { + MissingRetValue x = bar (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/coro-missing-ret-void.C b/gcc/testsuite/g++.dg/coroutines/coro-missing-ret-void.C new file mode 100644 index 00000000000..c9f84e59020 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro-missing-ret-void.C @@ -0,0 +1,34 @@ +// { dg-additional-options "-fsyntax-only -w" } + +#include "coro.h" + +// Diagose missing return_void() in the promise type. + +struct MissingRetVoid { + coro::coroutine_handle<> handle; + MissingRetVoid () : handle (nullptr) {} + MissingRetVoid (coro::coroutine_handle<> handle) : handle (handle) {} + struct missing_retvoid { + coro::suspend_never initial_suspend() { return {}; } + coro::suspend_never final_suspend() { return {}; } + MissingRetVoid get_return_object() { + return MissingRetVoid (coro::coroutine_handle::from_promise (*this)); + } + void unhandled_exception() { /*std::terminate();*/ }; + }; +}; + +template<> struct coro::coroutine_traits { + using promise_type = MissingRetVoid::missing_retvoid; +}; + +MissingRetVoid +bar () +{ + co_return; // { dg-error "no member named .return_void. in" } +} + +int main (int ac, char *av[]) { + MissingRetVoid x = bar (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh-1.C b/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh-1.C new file mode 100644 index 00000000000..3943e78d9d5 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh-1.C @@ -0,0 +1,17 @@ +// { dg-additional-options "-fsyntax-only -fexceptions -w" } + +// Diagose missing unhandled_exception() in the promise type. + +#include "coro.h" +#include "coro-missing-ueh.h" + +MissingUEH +bar () // { dg-error {no member named 'unhandled_exception' in} } +{ + co_return; +} + +int main (int ac, char *av[]) { + MissingUEH x = bar (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh-2.C b/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh-2.C new file mode 100644 index 00000000000..0f105c4c2d8 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh-2.C @@ -0,0 +1,18 @@ +// { dg-additional-options "-fsyntax-only -fno-exceptions " } + +// The missing method is warned for when exceptions are off and pedantic +// is on (default in the testsuite). + +#include "coro.h" +#include "coro-missing-ueh.h" + +MissingUEH +bar () // { dg-warning {no member named 'unhandled_exception' in} } +{ + co_return; +} + +int main (int ac, char *av[]) { + MissingUEH x = bar (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh-3.C b/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh-3.C new file mode 100644 index 00000000000..d775d8a630e --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh-3.C @@ -0,0 +1,18 @@ +// { dg-additional-options "-fsyntax-only -fno-exceptions -Wno-pedantic" } + +/* We don't warn about the missing method, unless in pedantic mode, so + this compile should be clean. */ + +#include "coro.h" +#include "coro-missing-ueh.h" + +MissingUEH +bar () +{ + co_return; +} + +int main (int ac, char *av[]) { + MissingUEH x = bar (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh.h b/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh.h new file mode 100644 index 00000000000..51e6135b8a7 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro-missing-ueh.h @@ -0,0 +1,23 @@ +#ifndef __MissingUEH_H +#define __MissingUEH_H + +/* Common code for testing missing unhandled_exception. */ +struct MissingUEH { + coro::coroutine_handle<> handle; + MissingUEH () : handle (nullptr) {} + MissingUEH (coro::coroutine_handle<> handle) : handle (handle) {} + struct missing_ueh { + coro::suspend_never initial_suspend() { return {}; } + coro::suspend_never final_suspend() { return {}; } + MissingUEH get_return_object() { + return MissingUEH (coro::coroutine_handle::from_promise (*this)); + } + void return_void () {} + }; +}; + +template<> struct coro::coroutine_traits { + using promise_type = MissingUEH::missing_ueh; +}; + +#endif diff --git a/gcc/testsuite/g++.dg/coroutines/coro-pre-proc.C b/gcc/testsuite/g++.dg/coroutines/coro-pre-proc.C new file mode 100644 index 00000000000..f22a5e08332 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro-pre-proc.C @@ -0,0 +1,9 @@ +// Only need to compile this, with the default options from the .exp. + +#ifndef __cpp_coroutines +#error "coroutines should engaged." +#endif + +#if __cpp_coroutines != 201902L +#error "coroutine version out of sync." +#endif diff --git a/gcc/testsuite/g++.dg/coroutines/coro.h b/gcc/testsuite/g++.dg/coroutines/coro.h new file mode 100644 index 00000000000..31336549f82 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro.h @@ -0,0 +1,152 @@ +#if __has_include() + +#include + +# if __clang__ +# include +# endif + +namespace coro = std; + +#elif __has_include() + +#include + +# if __clang__ +# include +# endif + +namespace coro = std::experimental; + +#else + +#warning "no installed coroutine headers found, using test-suite local one" + +/* Dummy version to allow tests without an installed header. */ +# ifndef __TESTSUITE_CORO_H_n4835 +# define __TESTSUITE_CORO_H_n4835 + +// Fragments (with short-cuts) to mimic enough of the library header to +// make some progress. + +# if __cpp_coroutines + +namespace std { +inline namespace __n4835 { + +// 21.11.1 coroutine traits +template struct coroutine_traits { + using promise_type = typename _R::promise_type; +}; + +// 21.11.2 coroutine handle +template struct coroutine_handle; + +template <> +struct coroutine_handle { + public: + // 21.11.2.1 construct/reset + constexpr coroutine_handle () noexcept + : __fr_ptr (0) {} + constexpr coroutine_handle (decltype(nullptr) __h) noexcept + : __fr_ptr (__h) {} + coroutine_handle &operator= (decltype(nullptr)) noexcept { + __fr_ptr = nullptr; + return *this; + } + + public: + // 21.11.2.2 export/import + constexpr void *address () const noexcept { return __fr_ptr; } + constexpr static coroutine_handle from_address (void *__a) noexcept { + coroutine_handle __self; + __self.__fr_ptr = __a; + return __self; + } + public: + // 21.11.2.3 observers + constexpr explicit operator bool () const noexcept { + return bool (__fr_ptr); + } + bool done () const noexcept { + return __builtin_coro_done (__fr_ptr); + } + // 21.11.2.4 resumption + void operator () () const { resume (); } + void resume () const { + __builtin_coro_resume (__fr_ptr); + } + void destroy () const { + __builtin_coro_destroy (__fr_ptr); + } + protected: + void *__fr_ptr; +}; + +template +struct coroutine_handle : coroutine_handle<> { + // 21.11.2.1 construct/reset + using coroutine_handle<>::coroutine_handle; + static coroutine_handle from_promise(_Promise &p) { + coroutine_handle __self; + __self.__fr_ptr = + __builtin_coro_promise((char *)&p, __alignof(_Promise), true); + return __self; + } + coroutine_handle& operator=(decltype(nullptr)) noexcept { + coroutine_handle<>::operator=(nullptr); + return *this; + } + // 21.11.2.2 export/import + constexpr static coroutine_handle from_address(void* __a){ + coroutine_handle __self; + __self.__fr_ptr = __a; + return __self; + } + // 21.11.2.5 promise access + _Promise& promise() const { + void * __t = __builtin_coro_promise(this->__fr_ptr, + __alignof(_Promise), false); + return *static_cast<_Promise*>(__t); + } +}; + +// n4760 - 21.11.5 trivial awaitables + +struct suspend_always { + bool await_ready() { return false; } + void await_suspend(coroutine_handle<>) {} + void await_resume() {} +}; + +struct suspend_never { + bool await_ready() { return true; } + void await_suspend(coroutine_handle<>) {} + void await_resume() {} +}; + +} // namespace __n4835 +} // namespace std + +namespace coro = std; + +# else +# error "coro.h requires support for coroutines, add -fcoroutines" +# endif +# endif // __TESTSUITE_CORO_H_n4835 + +#endif // __has_include() + +/* just to avoid cluttering dump files. */ +extern "C" int puts (const char *); +extern "C" int printf (const char *, ...); + +#include /* for abort () */ + +#ifndef OUTPUT +# define PRINT(X) +# define PRINTF (void) +#else +# define PRINT(X) puts(X) +# define PRINTF printf +#endif diff --git a/gcc/testsuite/g++.dg/coroutines/coro1-ret-int-yield-int.h b/gcc/testsuite/g++.dg/coroutines/coro1-ret-int-yield-int.h new file mode 100644 index 00000000000..b961755e472 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coro1-ret-int-yield-int.h @@ -0,0 +1,133 @@ +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + // Some awaitables to use in tests. + // With progress printing for debug. + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp");} + void await_resume() const noexcept { PRINT ("susp-never-resume");} + }; + + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp");} + void await_resume() const noexcept { PRINT ("susp-always-resume");} + ~suspend_always_prt() { PRINT ("susp-always-dtor"); } + }; + + struct suspend_always_intprt { + int x; + suspend_always_intprt() : x(5) {} + suspend_always_intprt(int __x) : x(__x) {} + ~suspend_always_intprt() {} + bool await_ready() const noexcept { return false; } + void await_suspend(coro::coroutine_handle<>) const noexcept { PRINT ("susp-always-susp-intprt");} + int await_resume() const noexcept { PRINT ("susp-always-resume-intprt"); return x;} + }; + + /* This returns the square of the int that it was constructed with. */ + struct suspend_always_longprtsq { + long x; + suspend_always_longprtsq() : x(12L) { PRINT ("suspend_always_longprtsq def ctor"); } + suspend_always_longprtsq(long _x) : x(_x) { PRINTF ("suspend_always_longprtsq ctor with %ld\n", x); } + ~suspend_always_longprtsq() {} + bool await_ready() const noexcept { return false; } + void await_suspend(coro::coroutine_handle<>) const noexcept { PRINT ("susp-always-susp-longsq");} + long await_resume() const noexcept { PRINT ("susp-always-resume-longsq"); return x * x;} + }; + + struct suspend_always_intrefprt { + int& x; + suspend_always_intrefprt(int& __x) : x(__x) {} + ~suspend_always_intrefprt() {} + bool await_ready() const noexcept { return false; } + void await_suspend(coro::coroutine_handle<>) const noexcept { PRINT ("susp-always-susp-intprt");} + int& await_resume() const noexcept { PRINT ("susp-always-resume-intprt"); return x;} + }; + + struct promise_type { + + promise_type() : vv(-1) { PRINT ("Created Promise"); } + promise_type(int __x) : vv(__x) { PRINTF ("Created Promise with %d\n",__x); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + + auto initial_suspend () { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + auto final_suspend () { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + +#ifdef USE_AWAIT_TRANSFORM + + auto await_transform (int v) { + PRINTF ("await_transform an int () %d\n",v); + return suspend_always_intprt (v); + } + + auto await_transform (long v) { + PRINTF ("await_transform a long () %ld\n",v); + return suspend_always_longprtsq (v); + } + +#endif + + auto yield_value (int v) { + PRINTF ("yield_value (%d)\n", v); + vv = v; + return suspend_always_prt{}; + } + +#ifdef RETURN_VOID + + void return_void () { + PRINT ("return_void ()"); + } + +#else + + void return_value (int v) { + PRINTF ("return_value (%d)\n", v); + vv = v; + } + +#endif + void unhandled_exception() { PRINT ("** unhandled exception"); } + + int get_value () { return vv; } + private: + int vv; + }; + +}; diff --git a/gcc/testsuite/g++.dg/coroutines/coroutines.exp b/gcc/testsuite/g++.dg/coroutines/coroutines.exp new file mode 100644 index 00000000000..e7fd4dac461 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/coroutines.exp @@ -0,0 +1,50 @@ +# Copyright (C) 2018-2020 Free Software Foundation, Inc. + +# Contributed by Iain Sandoe under contract to Facebook. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# . + +# Test C++ coroutines, requires c++17; doesn't, at present, seem much +# point in repeating these for other versions. + +# Load support procs. +load_lib g++-dg.exp + +# If a testcase doesn't have special options, use these. +global DEFAULT_CXXFLAGS +if ![info exists DEFAULT_CXXFLAGS] then { + set DEFAULT_CXXFLAGS " -pedantic-errors -Wno-long-long" +} + +set DEFAULT_COROFLAGS $DEFAULT_CXXFLAGS +lappend DEFAULT_COROFLAGS "-std=c++17" "-fcoroutines" + +dg-init + +# Run the tests. +# g++-dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.C]] \ +# "" $DEFAULT_COROFLAGS + +foreach test [lsort [find $srcdir/$subdir {*.[CH]}]] { + if [runtest_file_p $runtests $test] { + set nshort [file tail [file dirname $test]]/[file tail $test] + verbose "Testing $nshort $DEFAULT_COROFLAGS" 1 + dg-test $test "" $DEFAULT_COROFLAGS + set testcase [string range $test [string length "$srcdir/"] end] + } +} + +# done. +dg-finish diff --git a/gcc/testsuite/g++.dg/coroutines/torture/alloc-00-gro-on-alloc-fail.C b/gcc/testsuite/g++.dg/coroutines/torture/alloc-00-gro-on-alloc-fail.C new file mode 100644 index 00000000000..8430d053c65 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/alloc-00-gro-on-alloc-fail.C @@ -0,0 +1,118 @@ +// { dg-do run } + +// check the code-gen for the failed alloc return. + +#include "../coro.h" + +#if __has_include() +# include +#else + +// Required when get_return_object_on_allocation_failure() is defined by +// the promise. +// we need a no-throw new, and new etc. build the relevant pieces here to +// avoid needing the headers in the test. + +namespace std { + struct nothrow_t {}; + constexpr nothrow_t nothrow = {}; + typedef __SIZE_TYPE__ size_t; +} // end namespace std + +void* operator new(std::size_t, const std::nothrow_t&) noexcept; +void operator delete(void* __p, const std::nothrow_t&) noexcept; +#endif + +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () noexcept : handle(0) {} + coro1 (handle_type _handle) noexcept + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) noexcept : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) noexcept { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() noexcept { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp");} + void await_resume() const noexcept { PRINT ("susp-never-resume");} + ~suspend_never_prt() {}; + }; + + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp");} + void await_resume() const noexcept { PRINT ("susp-always-resume");} + }; + + struct promise_type { + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + auto initial_suspend () { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + auto final_suspend () { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + void return_void () { + PRINT ("return_void ()"); + } + void unhandled_exception() { PRINT ("** unhandled exception"); } + static coro1 get_return_object_on_allocation_failure () noexcept; + }; // promise +}; // coro1 + +coro1 coro1::promise_type:: +get_return_object_on_allocation_failure () noexcept { + PRINT ("alloc fail return"); + return coro1 (nullptr); +} + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/alloc-01-overload-newdel.C b/gcc/testsuite/g++.dg/coroutines/torture/alloc-01-overload-newdel.C new file mode 100644 index 00000000000..f779f6e4863 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/alloc-01-overload-newdel.C @@ -0,0 +1,120 @@ +// { dg-do run } + +// check codegen for overloaded operator new/delete. + +#include "../coro.h" + +int used_ovl_new = 0; +int used_ovl_del = 0; + +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () noexcept : handle(0) {} + coro1 (handle_type _handle) noexcept + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) noexcept : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) noexcept { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() noexcept { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp");} + void await_resume() const noexcept { PRINT ("susp-never-resume");} + ~suspend_never_prt() {}; + }; + + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp");} + void await_resume() const noexcept { PRINT ("susp-always-resume");} + }; + + struct promise_type { + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + void *operator new (std::size_t sz) { + PRINT ("promise_type: used overloaded operator new"); + used_ovl_new++; + return ::operator new(sz); + } + + void operator delete (void *p) { + PRINT ("promise_type: used overloaded operator delete"); + used_ovl_del++; + return ::operator delete(p); + } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + auto initial_suspend () { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + auto final_suspend () { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + void return_void () { + PRINT ("return_void ()"); + } + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; // promise +}; // coro1 + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return; +} + +int main () +{ + // Nest a scope so that we can inspect the flags after the DTORs run. + { + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + } + if (used_ovl_new != 1) + { + PRINT ("main: failed to call overloaded operator new"); + abort (); + } + if (used_ovl_del != 1) + { + PRINT ("main: failed to call overloaded operator delete"); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/call-00-co-aw-arg.C b/gcc/testsuite/g++.dg/coroutines/torture/call-00-co-aw-arg.C new file mode 100644 index 00000000000..ee108072f69 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/call-00-co-aw-arg.C @@ -0,0 +1,73 @@ +// { dg-do run } + +// Check that we can use co_await as a call parm. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +int gX = 1; + +__attribute__((__noinline__)) +static int +foo (int x) +{ + return x + 2; +} + +/* Function with a single await. */ +coro1 +f () +{ + gX = foo (co_await 9); + co_return gX + 31; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + + if (f_coro.handle.done()) + { + PRINT ("main: we should not be 'done' [1]"); + abort (); + } + + PRINT ("main: resuming [1] (initial suspend)"); + f_coro.handle.resume(); + PRINT ("main: resuming [2] (await 9 parm)"); + f_coro.handle.resume(); + + if (gX != 11) + { + PRINTF ("main: gX is wrong : %d, should be 11\n", gX); + abort (); + } + + /* we should now have returned with the co_return 11 + 31) */ + if (!f_coro.handle.done()) + { + PRINT ("main: we should be 'done'"); + abort (); + } + + int y = f_coro.handle.promise().get_value(); + if (y != 42) + { + PRINTF ("main: y is wrong : %d, should be 42\n", y); + abort (); + } + + puts ("main: done"); + return 0; +} \ No newline at end of file diff --git a/gcc/testsuite/g++.dg/coroutines/torture/call-01-multiple-co-aw.C b/gcc/testsuite/g++.dg/coroutines/torture/call-01-multiple-co-aw.C new file mode 100644 index 00000000000..0f5785163fc --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/call-01-multiple-co-aw.C @@ -0,0 +1,73 @@ +// { dg-do run } + +// Check that we can use multiple co_awaits as a call parm. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +int gX = 1; + +__attribute__((__noinline__)) +static int +bar (int x, int y) +{ + return x + y; +} + +/* Function with a multiple awaits. */ +coro1 +g () +{ + gX = bar (co_await 9, co_await 2); + co_return gX + 31; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 g_coro = g (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (g_coro.handle.done()) + { + PRINT ("main: we should not be 'done' [1]"); + abort (); + } + + PRINT ("main: resuming [1] (initial suspend)"); + g_coro.handle.resume(); + + PRINT ("main: resuming [2] (parm 1)"); + g_coro.handle.resume(); + PRINT ("main: resuming [2] (parm 2)"); + g_coro.handle.resume(); + if (gX != 11) + { + PRINTF ("main: gX is wrong : %d, should be 11\n", gX); + abort (); + } + + /* we should now have returned with the co_return 11 + 31) */ + if (!g_coro.handle.done()) + { + PRINT ("main: we should be 'done'"); + abort (); + } + + int y = g_coro.handle.promise().get_value(); + if (y != 42) + { + PRINTF ("main: y is wrong : %d, should be 42\n", y); + abort (); + } + + puts ("main: done"); + return 0; +} \ No newline at end of file diff --git a/gcc/testsuite/g++.dg/coroutines/torture/call-02-temp-co-aw.C b/gcc/testsuite/g++.dg/coroutines/torture/call-02-temp-co-aw.C new file mode 100644 index 00000000000..4982c49d796 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/call-02-temp-co-aw.C @@ -0,0 +1,72 @@ +// { dg-do run } + +// Check foo (compiler temp, co_await). + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +int gX = 1; + +__attribute__((__noinline__)) +static int +bar (int x, int y) +{ + return x + y; +} + +/* Function with a compiler temporary and a co_await. */ +coro1 +g () +{ + gX = bar (gX + 8, co_await 2); + co_return gX + 31; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 g_coro = g (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (g_coro.handle.done()) + { + PRINT ("main: we should not be 'done' [1]"); + abort (); + } + + PRINT ("main: resuming [1] (initial suspend)"); + g_coro.handle.resume(); + + PRINT ("main: resuming [2] (parm 1)"); + g_coro.handle.resume(); + + if (gX != 11) + { + PRINTF ("main: gX is wrong : %d, should be 11\n", gX); + abort (); + } + + /* we should now have returned with the co_return 11 + 31) */ + if (!g_coro.handle.done()) + { + PRINT ("main: we should be 'done'"); + abort (); + } + + int y = g_coro.handle.promise().get_value(); + if (y != 42) + { + PRINTF ("main: y is wrong : %d, should be 42\n", y); + abort (); + } + + puts ("main: done"); + return 0; +} \ No newline at end of file diff --git a/gcc/testsuite/g++.dg/coroutines/torture/call-03-temp-ref-co-aw.C b/gcc/testsuite/g++.dg/coroutines/torture/call-03-temp-ref-co-aw.C new file mode 100644 index 00000000000..d0bb4667ac9 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/call-03-temp-ref-co-aw.C @@ -0,0 +1,72 @@ +// { dg-do run } + +// Check foo (compiler temp, co_await). + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +int gX = 1; + +__attribute__((__noinline__)) +static int +bar (int x, const int& y) +{ + return x + y; +} + +/* Function with a compiler temporary and a co_await. */ +coro1 +g () +{ + gX = bar (gX + 8, co_await 2); + co_return gX + 31; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 g_coro = g (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (g_coro.handle.done()) + { + PRINT ("main: we should not be 'done' [1]"); + abort (); + } + + PRINT ("main: resuming [1] (initial suspend)"); + g_coro.handle.resume(); + + PRINT ("main: resuming [2] (parm 1)"); + g_coro.handle.resume(); + + if (gX != 11) + { + PRINTF ("main: gX is wrong : %d, should be 11\n", gX); + abort (); + } + + /* we should now have returned with the co_return 11 + 31) */ + if (!g_coro.handle.done()) + { + PRINT ("main: we should be 'done'"); + abort (); + } + + int y = g_coro.handle.promise().get_value(); + if (y != 42) + { + PRINTF ("main: y is wrong : %d, should be 42\n", y); + abort (); + } + + puts ("main: done"); + return 0; +} \ No newline at end of file diff --git a/gcc/testsuite/g++.dg/coroutines/torture/class-00-co-ret.C b/gcc/testsuite/g++.dg/coroutines/torture/class-00-co-ret.C new file mode 100644 index 00000000000..932fe4b2830 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/class-00-co-ret.C @@ -0,0 +1,41 @@ +// { dg-do run } + +// Simplest class. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +class foo +{ + public: + coro1 meth () + { + PRINT ("coro1: about to return"); + co_return 42; + } +}; + +int main () +{ + foo inst; + + PRINT ("main: create coro1"); + coro1 x = inst.meth (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/class-01-co-ret-parm.C b/gcc/testsuite/g++.dg/coroutines/torture/class-01-co-ret-parm.C new file mode 100644 index 00000000000..0bd477044b4 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/class-01-co-ret-parm.C @@ -0,0 +1,57 @@ +// { dg-do run } + +// Class with parm capture + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +class foo +{ + public: + coro1 meth (int x) + { + if (x > 30) + { + PRINT ("coro1: about to return k"); + co_return 6174; + } + else if (x > 20) + { + PRINT ("coro1: about to return the answer"); + co_return 42; + } + else + { + PRINT ("coro1: about to return 0"); + co_return 0; + } + } +}; + +int main () +{ + foo inst; + + PRINT ("main: create coro1"); + coro1 x = inst.meth (25); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + { + PRINTF ("main: wrong result (%d)", y); + abort (); + } + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/class-02-templ-parm.C b/gcc/testsuite/g++.dg/coroutines/torture/class-02-templ-parm.C new file mode 100644 index 00000000000..0cc6069c32f --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/class-02-templ-parm.C @@ -0,0 +1,52 @@ +// { dg-do run } + +// template parm in a class + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +template +class foo +{ + public: + coro1 meth (T y) + { + PRINT ("coro1: about to return"); + T x = y; + co_return co_await x + 3; + } +}; + +int main () +{ + foo inst {}; + PRINT ("main: create coro1"); + coro1 x = inst.meth (17); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume (initial suspend)"); + + x.handle.resume(); + PRINT ("main: after resume (co_await)"); + + /* Now we should have the co_returned value. */ + int y = x.handle.promise().get_value(); + if ( y != 20 ) + { + PRINTF ("main: wrong result (%d).", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/class-03-operator-templ-parm.C b/gcc/testsuite/g++.dg/coroutines/torture/class-03-operator-templ-parm.C new file mode 100644 index 00000000000..2d888a74558 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/class-03-operator-templ-parm.C @@ -0,0 +1,52 @@ +// { dg-do run } + +// template parm in a class + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +template +class foo +{ + public: + coro1 operator()(T y) + { + PRINT ("coro1: about to return"); + T x = y; + co_return co_await x + 3; + } +}; + +int main () +{ + foo inst {}; + PRINT ("main: create coro1"); + coro1 x = inst.operator()(17); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume (initial suspend)"); + + x.handle.resume(); + PRINT ("main: after resume (co_await)"); + + /* Now we should have the co_returned value. */ + int y = x.handle.promise().get_value(); + if ( y != 20 ) + { + PRINTF ("main: wrong result (%d).", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/class-04-lambda-1.C b/gcc/testsuite/g++.dg/coroutines/torture/class-04-lambda-1.C new file mode 100644 index 00000000000..e191c20ac06 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/class-04-lambda-1.C @@ -0,0 +1,58 @@ +// { dg-do run } + +// template parm in a class + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +template +class foo +{ + public: + auto get_lam () + { + auto l = [](T y) -> coro1 + { + T x = y; + co_return co_await x + 3; + }; + return l; + } +}; + +int main () +{ + foo inst {}; + auto ll = inst.get_lam (); + + PRINT ("main: create coro1"); + int arg = 17; // avoid a dangling reference + coro1 x = ll (arg); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume (initial suspend)"); + + x.handle.resume(); + PRINT ("main: after resume (co_await)"); + + /* Now we should have the co_returned value. */ + int y = x.handle.promise().get_value(); + if ( y != 20 ) + { + PRINTF ("main: wrong result (%d).", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/class-05-lambda-capture-copy-local.C b/gcc/testsuite/g++.dg/coroutines/torture/class-05-lambda-capture-copy-local.C new file mode 100644 index 00000000000..968940f5056 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/class-05-lambda-capture-copy-local.C @@ -0,0 +1,59 @@ +// { dg-do run } + +// template parm in a class + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +template +class foo +{ + public: + auto get_lam (int parm) + { + int local = 3; + auto l = [=](T y) -> coro1 + { + T x = y; + co_return co_await x + local; + }; + return l; + } +}; + +int main () +{ + foo inst {}; + auto ll = inst.get_lam (10); + + PRINT ("main: create coro1"); + int arg = 17; // avoid a dangling reference + coro1 x = ll (arg); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume (initial suspend)"); + + x.handle.resume(); + PRINT ("main: after resume (co_await)"); + + /* Now we should have the co_returned value. */ + int y = x.handle.promise().get_value(); + if ( y != 20 ) + { + PRINTF ("main: wrong result (%d).", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/class-06-lambda-capture-ref.C b/gcc/testsuite/g++.dg/coroutines/torture/class-06-lambda-capture-ref.C new file mode 100644 index 00000000000..db60132b0ee --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/class-06-lambda-capture-ref.C @@ -0,0 +1,59 @@ +// { dg-do run } + +// template parm in a class + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +template +class foo +{ + public: + void use_lambda () + { + int a_copy = 20; + int a_ref = 10; + + auto f = [&, a_copy]() -> coro1 + { + co_yield a_ref + a_copy; + co_return a_ref + a_copy; + }; + + coro1 A = f (); + A.handle.resume(); // Initial suspend. + PRINT ("main: [a_copy = 20, a_ref = 10]"); + + int y = A.handle.promise().get_value(); + if (y != 30) + { + PRINTF ("main: co-yield = %d, should be 30\n", y); + abort (); + } + + a_copy = 5; + a_ref = 7; + + A.handle.resume(); // from the yield. + PRINT ("main: [a_copy = 5, a_ref = 7]"); + + y = A.handle.promise().get_value(); + if (y != 27) + { + PRINTF ("main: co-ret = %d, should be 27\n", y); + abort (); + } + PRINT ("use_lambda: about to return"); + } + ~foo () { PRINT ("foo: DTOR"); } +}; + +int main () +{ + foo inst; + inst.use_lambda(); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-00-trivial.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-00-trivial.C new file mode 100644 index 00000000000..a24c2615997 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-00-trivial.C @@ -0,0 +1,52 @@ +// { dg-do run } + +// The simplest co_await we can do. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +int gX = 1; + +coro1 +f () +{ + co_await coro1::suspend_always_prt{}; + co_return gX + 10; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (f_coro.handle.done()) + { + PRINT ("main: we should not be 'done' [1]"); + abort (); + } + PRINT ("main: resuming [1] initial suspend"); + f_coro.handle.resume(); + PRINT ("main: resuming [2] co_await"); + f_coro.handle.resume(); + /* we should now have returned with the co_return (15) */ + if (!f_coro.handle.done()) + { + PRINT ("main: we should be 'done' "); + abort (); + } + int y = f_coro.handle.promise().get_value(); + if (y != 11) + { + PRINTF ("main: y is wrong : %d, should be 11\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-01-with-value.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-01-with-value.C new file mode 100644 index 00000000000..db5c90224d2 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-01-with-value.C @@ -0,0 +1,57 @@ +// { dg-do run } + +/* The simplest valued co_await we can do. */ + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +int gX = 1; + +coro1 +f () +{ + gX = co_await coro1::suspend_always_intprt{}; + co_return gX + 10; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (f_coro.handle.done()) + { + PRINT ("main: we should not be 'done' [1]"); + abort (); + } + PRINT ("main: resuming [1] initial suspend"); + f_coro.handle.resume(); + PRINT ("main: resuming [2] co_await suspend_always_intprt"); + f_coro.handle.resume(); + if (gX != 5) + { + PRINTF ("main: gX is wrong : %d, should be 5\n", gX); + abort (); + } + /* we should now have returned with the co_return (15) */ + if (!f_coro.handle.done()) + { + PRINT ("main: we should be 'done' "); + abort (); + } + int y = f_coro.handle.promise().get_value(); + if (y != 15) + { + PRINTF ("main: y is wrong : %d, should be 15\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-02-xform.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-02-xform.C new file mode 100644 index 00000000000..79ee6e17146 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-02-xform.C @@ -0,0 +1,58 @@ +// { dg-do run } + +// Test of basic await transform, no local state. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +int gX = 1; + +coro1 +f () +{ + gX = co_await 11; + co_return gX + 31; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (f_coro.handle.done()) + { + PRINT ("main: we should not be 'done' [1]"); + abort (); + } + PRINT ("main: resuming [1] initial suspend"); + f_coro.handle.resume(); + PRINT ("main: resuming [2] co_await"); + f_coro.handle.resume(); + if (gX != 11) + { + PRINTF ("main: gX is wrong : %d, should be 11\n", gX); + abort (); + } + /* we should now have returned with the co_return (15) */ + if (!f_coro.handle.done()) + { + PRINT ("main: we should be 'done' "); + abort (); + } + int y = f_coro.handle.promise().get_value(); + if (y != 42) + { + PRINTF ("main: y is wrong : %d, should be 42\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-03-rhs-op.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-03-rhs-op.C new file mode 100644 index 00000000000..64084325736 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-03-rhs-op.C @@ -0,0 +1,58 @@ +// { dg-do run } + +// Basic check of co_await with an expression to await transform. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +int gX = 1; + +coro1 +f () +{ + gX = co_await 11 + 15; + co_return gX + 16; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (f_coro.handle.done()) + { + PRINT ("main: we should not be 'done' [1]"); + abort (); + } + PRINT ("main: resuming [1] initial suspend"); + f_coro.handle.resume(); + PRINT ("main: resuming [1] await"); + f_coro.handle.resume(); + if (gX != 26) + { + PRINTF ("main: gX is wrong : %d, should be 26\n", gX); + abort (); + } + /* we should now have returned with the co_return (26+16) */ + if (!f_coro.handle.done()) + { + PRINT ("main: we should be 'done' "); + abort (); + } + int y = f_coro.handle.promise().get_value(); + if (y != 42) + { + PRINTF ("main: y is wrong : %d, should be 42\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-04-control-flow.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-04-control-flow.C new file mode 100644 index 00000000000..9bc99e875d0 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-04-control-flow.C @@ -0,0 +1,50 @@ +// { dg-do run } + +// Check correct operation of await transform. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +/* Valued with an await_transform. */ +int gX = 1; +int y = 30; + +coro1 +f () +{ + if (gX < 12) { + gX += y; + gX += co_await 11; + } else + gX += co_await 12; + + co_return gX; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + PRINT ("main: gX OK -- looping"); + do { + PRINTF ("main: gX : %d \n", gX); + f_coro.handle.resume(); + } while (!f_coro.handle.done()); + int y = f_coro.handle.promise().get_value(); + if (y != 42) + { + PRINTF ("main: y is wrong : %d, should be 42\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-05-loop.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-05-loop.C new file mode 100644 index 00000000000..34af740c99d --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-05-loop.C @@ -0,0 +1,51 @@ +// { dg-do run } + +// Check correct operation of co_await in a loop without local state. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +/* Valued with an await_transform. */ +int gX = 1; + +coro1 +f () +{ + for (;;) + { + gX += co_await 11; + if (gX > 100) + break; + } + co_return gX; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + PRINT ("main: gX OK -- looping"); + do { + PRINTF ("main: gX : %d \n", gX); + f_coro.handle.resume(); + } while (!f_coro.handle.done()); + + int y = f_coro.handle.promise().get_value(); + // first value above 100 is 10*11 + 1. + if (y != 111) + { + PRINTF ("main: y is wrong : %d, should be 111\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-06-ovl.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-06-ovl.C new file mode 100644 index 00000000000..14945faffd0 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-06-ovl.C @@ -0,0 +1,65 @@ +// { dg-do run } + +// Basic check of the co_await operator overload. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +/* A very simple overload. */ +struct empty +{ + auto operator co_await() const & noexcept { + return coro1::suspend_always_intprt{}; + } +}; + +int gX = 1; +empty e{}; + +coro1 +f () +{ + int a = co_await(e); /* operator ovl. */ + co_return gX + 5 + a; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (f_coro.handle.done()) + { + PRINT ("main: we should not be 'done'"); + abort (); + } + + PRINT ("main: resuming [1] initial suspend"); + f_coro.handle.resume(); + + PRINT ("main: resuming [2] co_await"); + f_coro.handle.resume(); + + /* we should now have returned with the co_return (11) */ + if (!f_coro.handle.done()) + { + PRINT ("main: we should be 'done' "); + abort (); + } + + int y = f_coro.handle.promise().get_value(); + if (y != 11) + { + PRINTF ("main: y is wrong : %d, should be 11\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-07-tmpl.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-07-tmpl.C new file mode 100644 index 00000000000..33f8e99d8c3 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-07-tmpl.C @@ -0,0 +1,132 @@ +// { dg-do run } + +// Check that we correctly operate when the coroutine object is templated. + +#include "../coro.h" + +template +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT ("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT ("Moved coro1"); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + return *this; + } + ~coro1() { + PRINT ("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + ~suspend_never_prt() {} + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type h) const noexcept { PRINT ("susp-never-susp");} + void await_resume() const noexcept {PRINT ("susp-never-resume");} + }; + + struct suspend_always_prt { + T x; + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp");} + void await_resume() const noexcept {PRINT ("susp-always-resume");} + }; + + /* This returns the int it was constructed with. */ + struct suspend_always_intprt { + T x; + suspend_always_intprt() : x((T)5) { PRINT ("suspend_always_intprt def ctor"); } + suspend_always_intprt(T _x) : x(_x) + { PRINTF ("suspend_always_intprt ctor with %ld\n", (long)x); } + ~suspend_always_intprt() {} + bool await_ready() const noexcept { return false; } + void await_suspend(coro::coroutine_handle<>) const noexcept { PRINT ("susp-always-susp-int");} + int await_resume() const noexcept { PRINT ("susp-always-resume-int"); return x;} + }; + + struct promise_type { + T value; + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + coro1 get_return_object() { + PRINT ("get_return_object: from handle from promise"); + return coro1 (handle_type::from_promise (*this)); + } + + auto initial_suspend() { + PRINT ("get initial_suspend "); + return suspend_never_prt{}; + } + + auto final_suspend() { + PRINT ("get final_suspend"); + return suspend_always_prt{}; + } + + void return_value (int v) { + PRINTF ("return_value () %ld\n", (long) v); + value = v; + } + + auto await_transform (T v) { + PRINTF ("await_transform a T () %ld\n", (long)v); + return suspend_always_intprt (v); + } + + T get_value () { return value; } + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; +}; + +/* Valued with an await_transform. */ +int gX = 2; + +template +coro1 f () +{ + for (int i = 0; i < 4; ++i) + { + gX += co_await 10; + } + co_return gX; +} + +int main () +{ + PRINT ("main: create coro1"); + auto f_coro = f(); + + PRINT ("main: got coro1 - checking gX"); + if (gX != 2) + { + PRINTF ("main: gX is wrong : %d, should be 2\n", gX); + abort (); + } + PRINT ("main: gX OK -- looping"); + do { + f_coro.handle.resume(); + } while (!f_coro.handle.done()); + + int y = f_coro.handle.promise().get_value(); + + if (y != 42) + { + PRINTF ("main: y is wrong : %d, should be 42\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-08-cascade.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-08-cascade.C new file mode 100644 index 00000000000..d34619d6b66 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-08-cascade.C @@ -0,0 +1,63 @@ +// { dg-do run } + +// Check cascaded co_await operations. + +#include "../coro.h" + +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +/* Valued with an await_transform. */ +int gX = 1; +coro1 f () +{ + /* We are going to use an await transform that takes a long, the + await_resume squares it. + so we get 11 ** 4, 14641. */ + gX = (int) co_await co_await 11L; + co_return gX + 31; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (f_coro.handle.done()) + { + PRINT ("main: we should not be 'done' [1]"); + abort (); + } + PRINT ("main: resuming [1] - inital suspend"); + f_coro.handle.resume(); + + PRINT ("main: resuming [2] - nested"); + f_coro.handle.resume(); + PRINT ("main: resuming [3] - outer"); + f_coro.handle.resume(); + + if (gX != 14641) + { + PRINTF ("main: gX is wrong : %d, should be 14641\n", gX); + abort (); + } + /* we should now have returned with the co_return (14672) */ + if (!f_coro.handle.done()) + { + PRINT ("main: we should be 'done' "); + abort (); + } + int y = f_coro.handle.promise().get_value(); + if (y != 14672) + { + PRINTF ("main: y is wrong : %d, should be 14672\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-09-pair.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-09-pair.C new file mode 100644 index 00000000000..525c6fc4677 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-09-pair.C @@ -0,0 +1,57 @@ +// { dg-do run } + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +/* Valued with an await_transform. */ +int gX = 1; +coro1 f () +{ + gX = co_await 11 + co_await 15; + co_return gX + 31; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (f_coro.handle.done()) + { + PRINT ("main: we should not be 'done' [1]"); + abort (); + } + PRINT ("main: resuming [1] (initial suspend)"); + f_coro.handle.resume(); + PRINT ("main: resuming [2] one side of add"); + f_coro.handle.resume(); + PRINT ("main: resuming [3] other side of add"); + f_coro.handle.resume(); + if (gX != 26) + { + PRINTF ("main: gX is wrong : %d, should be 26\n", gX); + abort (); + } + /* we should now have returned with the co_return (57) */ + if (!f_coro.handle.done()) + { + PRINT ("main: we should be 'done' "); + abort (); + } + int y = f_coro.handle.promise().get_value(); + if (y != 57) + { + PRINTF ("main: y is wrong : %d, should be 57\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-10-template-fn-arg.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-10-template-fn-arg.C new file mode 100644 index 00000000000..71a5b18c3cc --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-10-template-fn-arg.C @@ -0,0 +1,60 @@ +// { dg-do run } + +// Check type dependent function parms. + +#include "../coro.h" + +// boiler-plate for tests of codegen +// there is a promise ctor that takes a single int. +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +template +coro1 +f (T y) noexcept +{ + PRINT ("coro1: about to return"); + T x = y; + co_return co_await x + 3; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f(17); + + /* We should have created the promise with an initial value of + 17. */ + int y = x.handle.promise().get_value(); + if ( y != 17 ) + { + PRINTF ("main: wrong promise init (%d).", y); + abort (); + } + + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume (initial suspend)"); + + x.handle.resume(); + PRINT ("main: after resume (co_await)"); + + /* Now we should have the co_returned value. */ + y = x.handle.promise().get_value(); + if ( y != 20 ) + { + PRINTF ("main: wrong result (%d).", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-11-forwarding.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-11-forwarding.C new file mode 100644 index 00000000000..78c88ed14e9 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-11-forwarding.C @@ -0,0 +1,43 @@ +// { dg-do run } + +// Test of forwarding a templated awaitable to co_await. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +/* Valued with an await_transform. */ + +template< typename AWAITABLE > +coro1 +test_fwd (AWAITABLE&& awaitable) +{ + // the await_resume() just returns the saved int value. + int a = co_await std::forward(awaitable); + // Which we co-return to the promise so that it can be + // retrieved. + co_return a; +} + +int main () +{ + // We have an awaitable that stores the int it was constructed with. + coro1::suspend_always_intprt g(15); + struct coro1 g_coro = test_fwd (g); + + PRINT ("main: resuming g [1] (initial suspend)"); + g_coro.handle.resume(); + + PRINT ("main: resuming g [2] co_await"); + g_coro.handle.resume(); + + int y = g_coro.handle.promise().get_value(); + if (y != 15) + { + PRINTF ("main: y is wrong : %d, should be 15\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-12-operator-2.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-12-operator-2.C new file mode 100644 index 00000000000..189332b78e5 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-12-operator-2.C @@ -0,0 +1,66 @@ +// { dg-do run } + +// Basic check of the co_await operator overload. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +/* A very simple overload. */ +struct empty +{ + auto operator co_await() & noexcept { + return coro1::suspend_always_intprt{}; + } + auto operator co_await() && noexcept { + return coro1::suspend_always_longprtsq(3L); + } +}; + +empty e{}; + +coro1 +f () +{ + int a = co_await e; /* operator ovl lv. */ + int b = co_await empty{}; /* operator ovl rv. */ + co_return b + a; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + + if (f_coro.handle.done()) + { + PRINT ("main: we should not be 'done'"); + abort (); + } + + PRINT ("main: resuming [1] initial suspend"); + f_coro.handle.resume(); + + PRINT ("main: resuming [2] co_await a"); + f_coro.handle.resume(); + + PRINT ("main: resuming [3] co_await b"); + f_coro.handle.resume(); + + /* we should now have returned with the co_return (14) */ + if (!f_coro.handle.done()) + { + PRINT ("main: we should be 'done' "); + abort (); + } + + int y = f_coro.handle.promise().get_value(); + if (y != 14) + { + PRINTF ("main: y is wrong : %d, should be 14\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-await-13-return-ref.C b/gcc/testsuite/g++.dg/coroutines/torture/co-await-13-return-ref.C new file mode 100644 index 00000000000..339ebe4ff27 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-await-13-return-ref.C @@ -0,0 +1,58 @@ +// { dg-do run } + +/* The simplest valued co_await we can do. */ + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +int gX = 1; + +coro1 +f () +{ + int t = 5; + gX = co_await coro1::suspend_always_intrefprt{t}; + co_return t + 10; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + PRINT ("main: got coro1 - checking gX"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (f_coro.handle.done()) + { + PRINT ("main: we should not be 'done' [1]"); + abort (); + } + PRINT ("main: resuming [1] initial suspend"); + f_coro.handle.resume(); + PRINT ("main: resuming [2] co_await suspend_always_intprt"); + f_coro.handle.resume(); + if (gX != 5) + { + PRINTF ("main: gX is wrong : %d, should be 5\n", gX); + abort (); + } + /* we should now have returned with the co_return (15) */ + if (!f_coro.handle.done()) + { + PRINT ("main: we should be 'done' "); + abort (); + } + int y = f_coro.handle.promise().get_value(); + if (y != 15) + { + PRINTF ("main: y is wrong : %d, should be 15\n", y); + abort (); + } + puts ("main: done"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-00-void-return-is-ready.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-00-void-return-is-ready.C new file mode 100644 index 00000000000..f551c6e7607 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-00-void-return-is-ready.C @@ -0,0 +1,90 @@ +// { dg-do run } + +// Basic functionality check, co_return. +// Here we check the case that initial suspend is "never", so that the co- +// routine runs to completion immediately. + +#include "../coro.h" + +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp");} + void await_resume() const noexcept {PRINT ("susp-never-resume");} + ~suspend_never_prt() {}; + }; + + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp");} + void await_resume() const noexcept { PRINT ("susp-always-resume");} + }; + + struct promise_type { + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + coro1 get_return_object () { + PRINT ("get_return_object: from handle from promise"); + return coro1 (handle_type::from_promise (*this)); + } + auto initial_suspend () { + PRINT ("get initial_suspend (never) "); + return suspend_never_prt{}; + } + auto final_suspend () { + PRINT ("get final_suspend (always) "); + return suspend_always_prt{}; + } + void return_void () { + PRINT ("return_void ()"); + } + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; +}; + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - should be done"); + if (!x.handle.done()) + { + PRINT ("main: apparently was not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-01-void-return-is-suspend.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-01-void-return-is-suspend.C new file mode 100644 index 00000000000..03fc6eeb84b --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-01-void-return-is-suspend.C @@ -0,0 +1,94 @@ +// { dg-do run } + +// Basic functionality check, co_return. +// Here we check the case that initial suspend is "always". + +#include "../coro.h" + +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp");} + void await_resume() const noexcept { PRINT ("susp-never-resume");} + ~suspend_never_prt() {}; + }; + + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp");} + void await_resume() const noexcept { PRINT ("susp-always-resume");} + }; + + + struct promise_type { + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + coro1 get_return_object () { + PRINT ("get_return_object: from handle from promise"); + return coro1 (handle_type::from_promise (*this)); + } + auto initial_suspend () { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + auto final_suspend () { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + void return_void () { + PRINT ("return_void ()"); + } + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; +}; + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-03-different-GRO-type.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-03-different-GRO-type.C new file mode 100644 index 00000000000..36da680f7f9 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-03-different-GRO-type.C @@ -0,0 +1,92 @@ +// { dg-do run } + +// GRO differs from the eventual return type. + +# include "../coro.h" + +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp");} + void await_resume() const noexcept { PRINT ("susp-never-resume");} + ~suspend_never_prt() {}; + }; + + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp");} + void await_resume() const noexcept { PRINT ("susp-always-resume");} + }; + + struct promise_type { + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + auto initial_suspend () { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + auto final_suspend () { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + void return_void () { + PRINT ("return_void ()"); + } + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; +}; + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-04-GRO-nontriv.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-04-GRO-nontriv.C new file mode 100644 index 00000000000..29fb9424f82 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-04-GRO-nontriv.C @@ -0,0 +1,109 @@ +// { dg-do run } + +// GRO differs from eventual return type and has non-trivial dtor. + +#include "../coro.h" + +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + + struct nontriv { + handle_type handle; + nontriv () : handle(0) {PRINT("nontriv nul ctor");} + nontriv (handle_type _handle) + : handle(_handle) { + PRINT("Created nontriv object from handle"); + } + ~nontriv () { + PRINT("Destroyed nontriv"); + } + }; + + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (nontriv _nt) + : handle(_nt.handle) { + PRINT("Created coro1 object from nontriv"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp");} + void await_resume() const noexcept { PRINT ("susp-never-resume");} + ~suspend_never_prt() {}; + }; + + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp");} + void await_resume() const noexcept { PRINT ("susp-always-resume");} + }; + + struct promise_type { + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return nontriv(handle_type::from_promise (*this)); + } + auto initial_suspend () { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + auto final_suspend () { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + void return_void () { + PRINT ("return_void ()"); + } + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; +}; + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-05-return-value.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-05-return-value.C new file mode 100644 index 00000000000..42b80ff6bb6 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-05-return-value.C @@ -0,0 +1,38 @@ +// { dg-do run } + +// Test returning an int. +// We will use the promise to contain this to avoid having to include +// additional C++ headers. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return 42; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-06-template-promise-val-1.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-06-template-promise-val-1.C new file mode 100644 index 00000000000..5b1acb81457 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-06-template-promise-val-1.C @@ -0,0 +1,105 @@ +// { dg-do run } + +// Test returning a T. +// We will use the promise to contain this to avoid having to include +// additional C++ headers. + +#include "../coro.h" + +struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(coro::coroutine_handle<>) const noexcept + { PRINT ("susp-never-susp"); } + void await_resume() const noexcept { PRINT ("susp-never-resume");} +}; + +/* NOTE: this has a DTOR to test that pathway. */ +struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(coro::coroutine_handle<>) const noexcept + { PRINT ("susp-always-susp"); } + void await_resume() const noexcept { PRINT ("susp-always-resume"); } + ~suspend_always_prt() { PRINT ("susp-always-DTOR"); } +}; + +template +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct promise_type { + T value; + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + + auto initial_suspend () const { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + auto final_suspend () const { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + void return_value (T v) { + PRINTF ("return_value () %d\n",v); + value = v; + } + T get_value (void) { return value; } + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; +}; + +coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return (float) 42; +} + +int main () +{ + PRINT ("main: create coro1"); + coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != (float)42 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-07-void-cast-expr.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-07-void-cast-expr.C new file mode 100644 index 00000000000..b1a06f28495 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-07-void-cast-expr.C @@ -0,0 +1,44 @@ +// { dg-do run } + +// Check that "co_return (void)expression;" evaluates expression once. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define RETURN_VOID +#include "../coro1-ret-int-yield-int.h" + +int gX = 1; +__attribute__((__noinline__)) +int foo (void) { PRINT ("called the int fn foo"); gX +=1 ; return gX; } + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return (void)foo(); +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + // We want to check that foo() was called exactly once. + if (gX != 2) + { + PRINT ("main: failed check for a single call to foo()"); + abort (); + } + PRINT ("main: after resume"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-08-template-cast-ret.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-08-template-cast-ret.C new file mode 100644 index 00000000000..266bc7b3b26 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-08-template-cast-ret.C @@ -0,0 +1,104 @@ +// { dg-do run } + +// Test templated co-return. + +#include "../coro.h" + +struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(coro::coroutine_handle<>) const noexcept + { PRINT ("susp-never-susp"); } + void await_resume() const noexcept { PRINT ("susp-never-resume");} +}; + +/* NOTE: this has a DTOR to test that pathway. */ +struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(coro::coroutine_handle<>) const noexcept + { PRINT ("susp-always-susp"); } + void await_resume() const noexcept { PRINT ("susp-always-resume"); } + ~suspend_always_prt() { PRINT ("susp-always-DTOR"); } +}; + +template +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct promise_type { + T value; + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + suspend_always_prt initial_suspend () const { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + suspend_always_prt final_suspend () const { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + void return_value (T v) { + PRINTF ("return_value () %d\n",v); + value = v; + } + T get_value (void) { return value; } + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; +}; + +template +coro1 f () noexcept +{ + PRINT ("coro1: about to return"); + co_return (T)42; +} + +// The test will only really for int, but that's OK here. +int main () +{ + PRINT ("main: create coro1"); + auto x = f(); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + //x.handle.resume(); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-09-bool-await-susp.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-09-bool-await-susp.C new file mode 100644 index 00000000000..91f3f14cc09 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-09-bool-await-susp.C @@ -0,0 +1,97 @@ +// { dg-do run } + +// test boolean return from await_suspend (). + +#include "../coro.h" + +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + bool await_suspend(handle_type) const noexcept { + PRINT ("susp-never-susp"); // never executed. + return true; // ... + } + void await_resume() const noexcept {PRINT ("susp-never-resume");} + ~suspend_never_prt() {}; + }; + + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + bool await_suspend(handle_type) const noexcept { + PRINT ("susp-always-susp, but we're going to continue.. "); + return false; // not going to suspend. + } + void await_resume() const noexcept { PRINT ("susp-always-resume");} + }; + + struct promise_type { + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + coro1 get_return_object () { + PRINT ("get_return_object: from handle from promise"); + return coro1 (handle_type::from_promise (*this)); + } + auto initial_suspend () { + PRINT ("get initial_suspend (always, but really never) "); + return suspend_always_prt{}; + } + auto final_suspend () { + PRINT ("get final_suspend (always, but never) "); + return suspend_always_prt{}; + } + void return_void () { + PRINT ("return_void ()"); + } + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; +}; + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + auto p = x.handle.promise (); + auto aw = p.initial_suspend(); + auto f = aw.await_suspend(coro::coroutine_handle::from_address ((void *)&x)); + PRINT ("main: got coro1 - should be done"); + if (!x.handle.done()) + { + PRINT ("main: apparently was not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-10-expression-evaluates-once.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-10-expression-evaluates-once.C new file mode 100644 index 00000000000..7b07be5f448 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-10-expression-evaluates-once.C @@ -0,0 +1,49 @@ +// { dg-do run } + +// Check that "co_return expression;" only evaluates expression once. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +/* Give foo() a measureable side-effect. */ +int gX = 1; +__attribute__((__noinline__)) +int foo (void) +{ + PRINT ("called the int fn foo"); + gX += 1; + return gX; +} + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return foo(); +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + // We want to check that foo() was called exactly once. + if (gX != 2) + { + PRINT ("main: failed check for a single call to foo()"); + abort (); + } + PRINT ("main: after resume"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-11-co-ret-co-await.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-11-co-ret-co-await.C new file mode 100644 index 00000000000..06939107d80 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-11-co-ret-co-await.C @@ -0,0 +1,40 @@ +// { dg-do run } + +// Check co_return co_await + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return co_await coro1::suspend_always_intprt{}; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume 1 (initial suspend)"); + x.handle.resume(); + PRINT ("main: after resume 2 (await intprt)"); + + int y = x.handle.promise().get_value(); + if ( y != 5 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-12-co-ret-fun-co-await.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-12-co-ret-fun-co-await.C new file mode 100644 index 00000000000..50124c080b3 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-12-co-ret-fun-co-await.C @@ -0,0 +1,48 @@ +// { dg-do run } + +// Check co_return function (co_await) + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +__attribute__((__noinline__)) +static int +foo (int x) +{ + return x + 2; +} + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return foo (co_await 5); +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume 1 (initial suspend)"); + x.handle.resume(); + PRINT ("main: after resume 2 (await parm)"); + + int y = x.handle.promise().get_value(); + if ( y != 7 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-13-template-2.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-13-template-2.C new file mode 100644 index 00000000000..9d4a4de8ebe --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-13-template-2.C @@ -0,0 +1,56 @@ +// { dg-do run } + +// Check type dependent function parms. + +#include "../coro.h" + +// boiler-plate for tests of codegen +// there is a promise ctor that takes a single int. +#include "../coro1-ret-int-yield-int.h" + +template +coro1 +f (T y) noexcept +{ + PRINT ("coro1: about to return"); + T x = y; + co_return 3; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f(17); + + /* We should have created the promise with an initial value of + 17. */ + int y = x.handle.promise().get_value(); + if ( y != 17 ) + { + PRINT ("main: wrong promise init."); + abort (); + } + + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume (initial suspend)"); + + /* Now we should have the co_returned value. */ + y = x.handle.promise().get_value(); + if ( y != 3 ) + { + PRINT ("main: wrong answer."); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-ret-14-template-3.C b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-14-template-3.C new file mode 100644 index 00000000000..ebc1adba821 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-ret-14-template-3.C @@ -0,0 +1,58 @@ +// { dg-do run } + +// Check type dependent function parms. + +#include "../coro.h" + +// boiler-plate for tests of codegen +// there is a promise ctor that takes a single int. +#include "../coro1-ret-int-yield-int.h" + +template +coro1 +f (T x, U y, V z) noexcept +{ + PRINT ("coro1: about to return"); + T xi = (T) y; + T yi = (T) z; + T zi = x; + co_return 3 + xi + yi + zi; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f(2, 18.0F, 19.0); + + /* We should be using the default promise ctor, which sets the value + to -1. */ + int y = x.handle.promise().get_value(); + if ( y != -1 ) + { + PRINT ("main: wrong promise init."); + abort (); + } + + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume (initial suspend)"); + + /* Now we should have the co_returned value. */ + y = x.handle.promise().get_value(); + if ( y != 42 ) + { + PRINT ("main: wrong answer."); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-yield-00-triv.C b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-00-triv.C new file mode 100644 index 00000000000..586b6b25715 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-00-triv.C @@ -0,0 +1,129 @@ +// { dg-do run } + +// Test yielding an int. + +// We will use the promise to contain this to avoid having to include +// additional C++ headers. + +// Check that we resolve the correct overload for the yield_value method. + +#include "../coro.h" + +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp"); } + void await_resume() const noexcept { PRINT ("susp-never-resume");} + }; + + /* NOTE: this has a DTOR to test that pathway. */ + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp"); } + void await_resume() const noexcept { PRINT ("susp-always-resume"); } + ~suspend_always_prt() { PRINT ("susp-always-DTOR"); } + }; + + struct promise_type { + int value; + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + auto initial_suspend () { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + auto final_suspend () { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + void return_value (int v) { + PRINTF ("return_value () %d\n",v); + value = v; + } + auto yield_value (int v) { + PRINTF ("yield_value () %d and suspend always\n",v); + value = v; + return suspend_always_prt{}; + } + /* Some non-matching overloads. */ + auto yield_value (suspend_always_prt s, int x) { + return s; + } + auto yield_value (void) { + return 42; + } + int get_value (void) { return value; } + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; +}; + +struct coro1 +f () noexcept +{ + PRINT ("f: about to yield 42"); + co_yield 42; + + PRINT ("f: about to return 6174"); + co_return 6174; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming (1)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (1)"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + PRINT ("main: apparently got 42"); + PRINT ("main: got coro1 - resuming (2)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (2)"); + y = x.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + PRINT ("main: apparently got 6174"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-yield-01-multi.C b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-01-multi.C new file mode 100644 index 00000000000..5df69c7f156 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-01-multi.C @@ -0,0 +1,64 @@ +// { dg-do run } + +// Test yielding an int. +// We will use the promise to contain this to avoid having to include +// additional C++ headers. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f () noexcept +{ + PRINT ("f: about to yield 42"); + co_yield 42; + + PRINT ("f: about to yield 11"); + co_yield 11; + + PRINT ("f: about to return 6174"); + co_return 6174; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming (1)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (1)"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + PRINT ("main: apparently got 42 - resuming (2)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (2)"); + y = x.handle.promise().get_value(); + if ( y != 11 ) + abort (); + PRINT ("main: apparently got 11 - resuming (3)"); + if (x.handle.done()) + { + PRINT ("main: done?"); + abort(); + } + x.handle.resume(); + PRINT ("main: after resume (2) checking return"); + y = x.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + PRINT ("main: apparently got 6174"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-yield-02-loop.C b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-02-loop.C new file mode 100644 index 00000000000..8d4f1d5d823 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-02-loop.C @@ -0,0 +1,68 @@ +// { dg-do run } + +// Test co_yield in a loop with no local state. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +int gX = 1; + +struct coro1 +f () noexcept +{ + for (gX = 5; gX < 10 ; gX++) + { + PRINTF ("f: about to yield %d\n", gX); + co_yield gX; + } + + PRINT ("f: about to return 6174"); + co_return 6174; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 f_coro = f (); + PRINT ("main: got coro1 - resuming (1)"); + if (gX != 1) + { + PRINTF ("main: gX is wrong : %d, should be 1\n", gX); + abort (); + } + if (f_coro.handle.done()) + abort(); + f_coro.handle.resume(); + PRINT ("main: after resume (1)"); + int y = f_coro.handle.promise().get_value(); + if (y != 5) + { + PRINTF ("main: got %d not 5.\n",y); + abort (); + } + PRINT ("main: gX OK -- looping"); + do { + y = f_coro.handle.promise().get_value(); + if (y != gX) + { + PRINTF ("main: got %d not %d.\n",y, gX); + abort (); + } + PRINTF ("main: gX : %d \n", gX); + f_coro.handle.resume(); + } while (!f_coro.handle.done()); + + y = f_coro.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + PRINT ("main: apparently got 6174"); + if (!f_coro.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-yield-03-tmpl.C b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-03-tmpl.C new file mode 100644 index 00000000000..cceee1f19e9 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-03-tmpl.C @@ -0,0 +1,140 @@ +// { dg-do run } + +// Test co_yield in templated code. + +#include "../coro.h" + +template +struct looper { + + struct promise_type { + T value; + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + + auto initial_suspend () { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + + auto final_suspend () { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + + void return_value (T v) { + PRINTF ("return_value () %lf\n", (double)v); + value = v; + } + + auto yield_value (T v) { + PRINTF ("yield_value () %lf and suspend always\n", (double)v); + value = v; + return suspend_always_prt{}; + } + + T get_value (void) { return value; } + + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; + + using handle_type = coro::coroutine_handle; + handle_type handle; + + looper () : handle(0) {} + looper (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + looper (const looper &) = delete; // no copying + looper (looper &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("looper mv ctor "); + } + looper &operator = (looper &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("looper op= "); + return *this; + } + ~looper() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp"); } + void await_resume() const noexcept { PRINT ("susp-never-resume");} + }; + + /* NOTE: this has a DTOR to test that pathway. */ + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp"); } + void await_resume() const noexcept { PRINT ("susp-always-resume"); } + ~suspend_always_prt() { PRINT ("susp-always-DTOR"); } + }; + +}; + +// Contrived to avoid non-scalar state across the yield. +template +looper f () noexcept +{ + for (int i = 5; i < 10 ; ++i) + { + PRINTF ("f: about to yield %d\n", i); + co_yield (T) i; + } + + PRINT ("f: about to return 6174"); + co_return 6174; +} + +// contrived, only going to work for an int. +int main () +{ + PRINT ("main: create int looper"); + auto f_coro = f (); + + if (f_coro.handle.done()) + { + PRINT ("main: said we were done, but we hadn't started!"); + abort(); + } + + PRINT ("main: OK -- looping"); + int y, test = 5; + do { + f_coro.handle.resume(); + if (f_coro.handle.done()) + break; + y = f_coro.handle.promise().get_value(); + if (y != test) + { + PRINTF ("main: failed for test %d, got %d\n", test, y); + abort(); + } + test++; + } while (test < 20); + + y = f_coro.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + + PRINT ("main: apparently got 6174"); + if (!f_coro.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-yield-04-complex-local-state.C b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-04-complex-local-state.C new file mode 100644 index 00000000000..d9330b33b76 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-04-complex-local-state.C @@ -0,0 +1,162 @@ +// { dg-do run } + +// using non-trivial types in the coro. + +# include "../coro.h" + +#include +#include + +template +struct looper { + + struct promise_type { + T value; + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + + auto initial_suspend () { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + + auto final_suspend () { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + + void return_value (T v) { + PRINTF ("return_value () %s\n", v.c_str()); + value = v; + } + + auto yield_value (T v) { + PRINTF ("yield_value () %s and suspend always\n", v.c_str()); + value = v; + return suspend_always_prt{}; + } + + T get_value (void) { return value; } + + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; + + using handle_type = coro::coroutine_handle; + handle_type handle; + + looper () : handle(0) {} + looper (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + looper (const looper &) = delete; // no copying + looper (looper &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("looper mv ctor "); + } + looper &operator = (looper &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("looper op= "); + return *this; + } + ~looper() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp"); } + void await_resume() const noexcept { PRINT ("susp-never-resume");} + }; + + /* NOTE: this has a DTOR to test that pathway. */ + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp"); } + void await_resume() const noexcept { PRINT ("susp-always-resume"); } + ~suspend_always_prt() { PRINT ("susp-always-DTOR"); } + }; + +}; + +int gX ; + +struct mycounter +{ + mycounter () : v(0) { PRINT ("mycounter CTOR"); } + ~mycounter () { gX = 6174; PRINT ("mycounter DTOR"); } + int value () { return v; } + void incr () { v++; } + int v; +}; + +template +looper with_ctorable_state (std::vector d) noexcept +{ + std::vector loc; + unsigned lim = d.size()-1; + mycounter c; + for (unsigned i = 0; i < lim ; ++i) + { + loc.push_back(d[i]); + c.incr(); + PRINTF ("f: about to yield value %d \n", i); + co_yield loc[i]; + } + loc.push_back(d[lim]); + + PRINT ("f: done"); + co_return loc[lim]; +} + +int main () +{ + PRINT ("main: create looper"); + std::vector input = {"first", "the", "quick", "reddish", "fox", "done" }; + auto f_coro = with_ctorable_state (input); + + PRINT ("main: got looper - resuming (1)"); + if (f_coro.handle.done()) + abort(); + + f_coro.handle.resume(); + std::string s = f_coro.handle.promise().get_value(); + if ( s != "first" ) + abort (); + + PRINTF ("main: got : %s\n", s.c_str()); + unsigned check = 1; + do { + f_coro.handle.resume(); + s = f_coro.handle.promise().get_value(); + if (s != input[check++]) + abort (); + PRINTF ("main: got : %s\n", s.c_str()); + } while (!f_coro.handle.done()); + + if ( s != "done" ) + abort (); + + PRINT ("main: should be done"); + if (!f_coro.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + + if (gX != 6174) + { + PRINT ("main: apparently we didn't run mycounter DTOR..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-yield-05-co-aw.C b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-05-co-aw.C new file mode 100644 index 00000000000..043f97b6e1b --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-05-co-aw.C @@ -0,0 +1,55 @@ +// { dg-do run } + +// Check co_return co_await + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f () noexcept +{ + PRINT ("f: about to yield"); + co_yield co_await coro1::suspend_always_intprt(42); + + PRINT ("f: about to return 6174"); + co_return 6174; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + if (x.handle.done()) + abort(); + + PRINT ("main: resuming (initial suspend)"); + x.handle.resume(); + PRINT ("main: resuming (await intprt)"); + x.handle.resume(); + + PRINT ("main: after resume (2)"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + PRINT ("main: apparently got 42"); + + PRINT ("main: got coro1 - resuming (co_yield)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + + PRINT ("main: after resume (co_yield)"); + y = x.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + PRINT ("main: apparently got 6174"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-yield-06-fun-parm.C b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-06-fun-parm.C new file mode 100644 index 00000000000..c74e44d15d5 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-06-fun-parm.C @@ -0,0 +1,64 @@ +// { dg-do run } + +// Check co_return co_await + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +__attribute__((__noinline__)) +static int +foo (int x) +{ + return x + 2; +} + +/* Function with a single await. */ +struct coro1 +f () noexcept +{ + PRINT ("f: about to yield"); + co_yield foo (co_await 40); + + PRINT ("f: about to return 6174"); + co_return 6174; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + if (x.handle.done()) + abort(); + + PRINT ("main: resuming (initial suspend)"); + x.handle.resume(); + PRINT ("main: resuming (await intprt)"); + x.handle.resume(); + + PRINT ("main: after resume (2)"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + PRINT ("main: apparently got 42"); + + PRINT ("main: got coro1 - resuming (co_yield)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + + PRINT ("main: after resume (co_yield)"); + y = x.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + PRINT ("main: apparently got 6174"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-yield-07-template-fn-param.C b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-07-template-fn-param.C new file mode 100644 index 00000000000..74dae633955 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-07-template-fn-param.C @@ -0,0 +1,71 @@ +// { dg-do run } + +// Check type dependent function parms. + +#include "../coro.h" + +// boiler-plate for tests of codegen +// there is a promise ctor that takes a single int. + +#include "../coro1-ret-int-yield-int.h" + +template +coro1 +f (T y) noexcept +{ + PRINT ("coro1: about to return"); + T x = y; + co_yield x + 3; + co_return 42; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f(17); + + /* We should have created the promise with an initial value of + 17. */ + int y = x.handle.promise().get_value(); + if ( y != 17 ) + { + PRINTF ("main: wrong promise init (%d).", y); + abort (); + } + if (x.handle.done()) + abort(); + + PRINT ("main: got coro1 - resuming"); + x.handle.resume(); + PRINT ("main: after resume (initial suspend)"); + + if (x.handle.done()) + abort(); + + /* Now we should have the co_yielded value. */ + y = x.handle.promise().get_value(); + if ( y != 20 ) + { + PRINTF ("main: wrong result (%d).", y); + abort (); + } + + PRINT ("main: after resume (co_yield)"); + x.handle.resume(); + + /* now we should have the co_returned value. */ + y = x.handle.promise().get_value(); + if ( y != 42 ) + { + PRINTF ("main: wrong result (%d).", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-yield-08-more-refs.C b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-08-more-refs.C new file mode 100644 index 00000000000..8e39127a1ae --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-08-more-refs.C @@ -0,0 +1,68 @@ +// { dg-do run } + +// Check co_return co_await + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +/* Tests for . */ +struct test +{ + auto operator co_await() & noexcept { + return coro1::suspend_always_intprt{}; + } + + auto operator co_await() && noexcept { + return coro1::suspend_always_longprtsq(3L); + } +}; + +struct coro1 +f (test thing) noexcept +{ + co_yield co_await static_cast(thing); + co_return 6174; +} + +int main () +{ + PRINT ("main: create coro1"); + + struct coro1 x = f (test{}); + if (x.handle.done()) + abort(); + + PRINT ("main: resuming (initial suspend)"); + x.handle.resume(); + PRINT ("main: resuming (await intprt)"); + x.handle.resume(); + + int y = x.handle.promise().get_value(); + if ( y != 9 ) + { + PRINTF ("main: co-yield gave %d, should be 9\n", y); + abort (); + } + + PRINT ("main: got coro1 - resuming (co_yield)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + + y = x.handle.promise().get_value(); + if ( y != 6174 ) + { + PRINTF ("main: co-return gave %d, should be 9\n", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/co-yield-09-more-templ-refs.C b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-09-more-templ-refs.C new file mode 100644 index 00000000000..3abbe1c43ab --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/co-yield-09-more-templ-refs.C @@ -0,0 +1,68 @@ +// { dg-do run } + +// Check co_return co_await + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +/* A very simple overload. */ +struct test +{ + auto operator co_await() & noexcept { + return coro1::suspend_always_intprt{}; + } + + auto operator co_await() && noexcept { + return coro1::suspend_always_longprtsq(3L); + } +}; + +template +RESULT +f (PARAM thing) noexcept +{ + co_yield co_await static_cast(thing); + co_return 6174; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (test{}); + if (x.handle.done()) + abort(); + + PRINT ("main: resuming (initial suspend)"); + x.handle.resume(); + PRINT ("main: resuming (await intprt)"); + x.handle.resume(); + + int y = x.handle.promise().get_value(); + if ( y != 9 ) + { + PRINTF ("main: co-yield gave %d, should be 9\n", y); + abort (); + } + + PRINT ("main: got coro1 - resuming (co_yield)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + + y = x.handle.promise().get_value(); + if ( y != 6174 ) + { + PRINTF ("main: co-return gave %d, should be 9\n", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/coro-torture.exp b/gcc/testsuite/g++.dg/coroutines/torture/coro-torture.exp new file mode 100644 index 00000000000..d2463b27983 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/coro-torture.exp @@ -0,0 +1,19 @@ +# This harness is for tests that should be run at all optimisation levels. + +load_lib g++-dg.exp +load_lib torture-options.exp + +global DG_TORTURE_OPTIONS LTO_TORTURE_OPTIONS + +dg-init +torture-init + +set DEFAULT_COROFLAGS $DEFAULT_CXXFLAGS +lappend DEFAULT_COROFLAGS "-std=c++17" "-fcoroutines" + +set-torture-options [concat $DG_TORTURE_OPTIONS $LTO_TORTURE_OPTIONS] + +gcc-dg-runtest [lsort [glob $srcdir/$subdir/*.C]] "" $DEFAULT_COROFLAGS + +torture-finish +dg-finish diff --git a/gcc/testsuite/g++.dg/coroutines/torture/exceptions-test-0.C b/gcc/testsuite/g++.dg/coroutines/torture/exceptions-test-0.C new file mode 100644 index 00000000000..164c804797d --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/exceptions-test-0.C @@ -0,0 +1,167 @@ +// { dg-do run } + +// Test exceptions. + +#include "../coro.h" +#include + +int gX = 0; + +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() { + PRINT("Destroyed coro1"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp"); } + void await_resume() const noexcept { PRINT ("susp-never-resume");} + }; + + /* NOTE: this has a DTOR to test that pathway. */ + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp"); } + void await_resume() const noexcept { PRINT ("susp-always-resume"); } + ~suspend_always_prt() { PRINT ("susp-always-DTOR"); } + }; + + struct promise_type { + int value; + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { PRINT ("Destroyed Promise"); } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + auto initial_suspend () { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + auto final_suspend () { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + void return_value (int v) { + PRINTF ("return_value () %d\n",v); + value = v; + } + auto yield_value (int v) { + PRINTF ("yield_value () %d and suspend always\n",v); + value = v; + return suspend_always_prt{}; + } + /* Some non-matching overloads. */ + auto yield_value (suspend_always_prt s, int x) { + return s; + } + auto yield_value (void) { + return 42;//suspend_always_prt{}; + } + int get_value (void) { return value; } + + void unhandled_exception() { + PRINT ("unhandled_exception: caught one!"); + gX = -1; + // returning from here should end up in final_suspend. + } + }; +}; + +// So we want to check that the internal behaviour of try/catch is +// working OK - and that if we have an unhandled exception it is caught +// by the wrapper that we add to the rewritten func. + +struct coro1 throw_and_catch () noexcept +{ + int caught = 0; + + try { + PRINT ("f: about to yield 42"); + co_yield 42; + + throw (20); + + PRINT ("f: about to yield 6174"); + co_return 6174; + + } catch (int x) { + PRINTF ("f: caught %d\n", x); + caught = x; + } + + PRINTF ("f: about to yield what we caught %d\n", caught); + co_yield caught; + + throw ("bah"); + + PRINT ("f: about to return 22"); + co_return 22; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = throw_and_catch (); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: got coro, resuming.."); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + PRINT ("main: apparently got the expected 42"); + if (x.handle.done()) + abort(); + PRINT ("main: resuming..."); + x.handle.resume(); + + y = x.handle.promise().get_value(); + if ( y != 20 ) + abort (); + PRINT ("main: apparently got 20, which we expected"); + if (x.handle.done()) + abort(); + + PRINT ("main: resuming..."); + x.handle.resume(); + // This should cause the throw of "bah" which is unhandled. + // We should catch the unhandled exception and then fall through + // to the final suspend point... thus be "done". + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + // When we caught the unhandled exception we flagged it instead of + // std::terminate-ing. + if (gX != -1) + { + PRINT ("main: apparently failed to catch"); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/func-params-00.C b/gcc/testsuite/g++.dg/coroutines/torture/func-params-00.C new file mode 100644 index 00000000000..b5716972d47 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/func-params-00.C @@ -0,0 +1,42 @@ +// { dg-do run } + +// Test promise construction from function args list. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f (int x) noexcept +{ + PRINT ("coro1: about to return"); + co_return 42; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (555); + int y = x.handle.promise().get_value(); + if ( y != 555 ) + { + PRINT ("main: incorrect ctor value"); + abort (); + } + PRINTF ("main: after coro1 ctor %d - now resuming\n", y); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/func-params-01.C b/gcc/testsuite/g++.dg/coroutines/torture/func-params-01.C new file mode 100644 index 00000000000..f530431a6bb --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/func-params-01.C @@ -0,0 +1,45 @@ +// { dg-do run } + +// Simplest test that we correctly handle function params in the body +// of the coroutine. No local state, just the parm. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f (int x) noexcept +{ + if (x > 20) + { + PRINT ("coro1: about to return k"); + co_return 6174; + } + else + { + PRINT ("coro1: about to return the answer"); + co_return 42; + } +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (32); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/func-params-02.C b/gcc/testsuite/g++.dg/coroutines/torture/func-params-02.C new file mode 100644 index 00000000000..396b438cb2d --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/func-params-02.C @@ -0,0 +1,50 @@ +// { dg-do run } + +// Test that we correctly re-write multiple uses of a function param +// in the body. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f (int x) noexcept +{ + if (x > 30) + { + PRINT ("coro1: about to return k"); + co_return 6174; + } + else if (x > 20) + { + PRINT ("coro1: about to return the answer"); + co_return 42; + } + else + { + PRINT ("coro1: about to return 0"); + co_return 0; + } +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (25); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/func-params-03.C b/gcc/testsuite/g++.dg/coroutines/torture/func-params-03.C new file mode 100644 index 00000000000..bf699722a1a --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/func-params-03.C @@ -0,0 +1,49 @@ +// { dg-do run } + +// Test that we can use a function param in a co_xxxx status. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f (int x) noexcept +{ + if (x > 30) + { + PRINT ("coro1: about to return k"); + co_return 6174; + } + else if (x > 20) + { + PRINTF ("coro1: about to co-return %d", x); + co_return x; + } + else + { + PRINT ("coro1: about to return 0"); + co_return 0; + } +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (25); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 25 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/func-params-04.C b/gcc/testsuite/g++.dg/coroutines/torture/func-params-04.C new file mode 100644 index 00000000000..789e2c05b69 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/func-params-04.C @@ -0,0 +1,57 @@ +// { dg-do run } + +// Test that we can manage a constructed param copy. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +// Require a ctor. +struct nontriv { + int a, b, c; + nontriv (int _a, int _b, int _c) : a(_a), b(_b), c(_c) {} + virtual int getA () { return a; } +}; + +struct coro1 +f (nontriv t) noexcept +{ + if (t.a > 30) + { + PRINTF ("coro1: about to return %d", t.b); + co_return t.b; + } + else if (t.a > 20) + { + PRINTF ("coro1: about to co-return %d", t.c); + co_return t.c; + } + else + { + PRINT ("coro1: about to return 0"); + co_return 0; + } +} + +int main () +{ + PRINT ("main: create coro1"); + nontriv test (25, 6174, 42); + struct coro1 x = f (test); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/func-params-05.C b/gcc/testsuite/g++.dg/coroutines/torture/func-params-05.C new file mode 100644 index 00000000000..8bdb2b5d0f7 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/func-params-05.C @@ -0,0 +1,57 @@ +// { dg-do run } + +// Test that we can manage a constructed param reference + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +// Require a ctor. +struct nontriv { + int a, b, c; + nontriv (int _a, int _b, int _c) : a(_a), b(_b), c(_c) {} + virtual int getA () { return a; } +}; + +struct coro1 +f (nontriv &t) noexcept +{ + if (t.a > 30) + { + PRINTF ("coro1: about to return %d", t.b); + co_return t.b; + } + else if (t.a > 20) + { + PRINTF ("coro1: about to co-return %d", t.c); + co_return t.c; + } + else + { + PRINT ("coro1: about to return 0"); + co_return 0; + } +} + +int main () +{ + PRINT ("main: create coro1"); + nontriv test (25, 6174, 42); + struct coro1 x = f (test); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/func-params-06.C b/gcc/testsuite/g++.dg/coroutines/torture/func-params-06.C new file mode 100644 index 00000000000..cbcfe67ff1a --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/func-params-06.C @@ -0,0 +1,47 @@ +// { dg-do run } + +// check references are handled as expected. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +coro1 +f (int& a_ref, int a_copy) +{ + co_yield a_ref + a_copy; + co_return a_ref + a_copy; +} + +int main () +{ + int a_copy = 20; + int a_ref = 10; + + coro1 A = f (a_ref, a_copy); + A.handle.resume(); // Initial suspend. + PRINT ("main: [a_copy = 20, a_ref = 10]"); + + int y = A.handle.promise().get_value(); + if (y != 30) + { + PRINTF ("main: co-yield = %d, should be 30\n", y); + abort (); + } + + a_copy = 5; + a_ref = 7; + + A.handle.resume(); + PRINT ("main: [a_copy = 5, a_ref = 7]"); + + y = A.handle.promise().get_value(); + if (y != 27) + { + PRINTF ("main: co-ret = %d, should be 27\n", y); + abort (); + } + + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/lambda-00-co-ret.C b/gcc/testsuite/g++.dg/coroutines/torture/lambda-00-co-ret.C new file mode 100644 index 00000000000..61e284d5c8f --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/lambda-00-co-ret.C @@ -0,0 +1,35 @@ +// { dg-do run } + +// Simplest lambda + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +int main () +{ + auto f = []() -> coro1 + { + PRINT ("coro1: about to return"); + co_return 42; + }; + + PRINT ("main: create coro1"); + coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/lambda-01-co-ret-parm.C b/gcc/testsuite/g++.dg/coroutines/torture/lambda-01-co-ret-parm.C new file mode 100644 index 00000000000..378eedc6d89 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/lambda-01-co-ret-parm.C @@ -0,0 +1,48 @@ +// { dg-do run } + +// Lambda with parm + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +int main () +{ + auto f = [](int x) -> coro1 + { + if (x > 30) + { + PRINT ("coro1: about to return k"); + co_return 6174; + } + else if (x > 20) + { + PRINT ("coro1: about to return the answer"); + co_return 42; + } + else + { + PRINT ("coro1: about to return 0"); + co_return 0; + } + }; + + PRINT ("main: create coro1"); + coro1 x = f (25); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/lambda-02-co-yield-values.C b/gcc/testsuite/g++.dg/coroutines/torture/lambda-02-co-yield-values.C new file mode 100644 index 00000000000..a6f592cd77a --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/lambda-02-co-yield-values.C @@ -0,0 +1,64 @@ +// { dg-do run } + +// lambda with parm and local state + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +int main () +{ + auto f = [](int start) -> coro1 + { + int value = start; + PRINT ("f: about to yield start"); + co_yield start; + + value -= 31; + PRINT ("f: about to yield (value-31)"); + co_yield value; + + value += 6163; + PRINT ("f: about to return (value+6163)"); + co_return value; + }; + + PRINT ("main: create coro1"); + coro1 x = f (42); + PRINT ("main: got coro1 - resuming (1)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (1)"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + PRINT ("main: apparently got 42 - resuming (2)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (2)"); + y = x.handle.promise().get_value(); + if ( y != 11 ) + abort (); + PRINT ("main: apparently got 11 - resuming (3)"); + if (x.handle.done()) + { + PRINT ("main: done?"); + abort(); + } + x.handle.resume(); + PRINT ("main: after resume (2) checking return"); + y = x.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + PRINT ("main: apparently got 6174"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/lambda-03-auto-parm-1.C b/gcc/testsuite/g++.dg/coroutines/torture/lambda-03-auto-parm-1.C new file mode 100644 index 00000000000..bfa5400225d --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/lambda-03-auto-parm-1.C @@ -0,0 +1,46 @@ +// { dg-do run } + +// generic Lambda with auto parm (c++14) + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +int main () +{ + auto f = [](auto y) -> coro1 + { + PRINT ("coro1: about to return"); + auto x = y; + co_return co_await x + 3; + }; + + PRINT ("main: create coro1"); + struct coro1 x = f((int)17); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume (initial suspend)"); + + x.handle.resume(); + PRINT ("main: after resume (co_await)"); + + /* Now we should have the co_returned value. */ + int y = x.handle.promise().get_value(); + if ( y != 20 ) + { + PRINTF ("main: wrong result (%d).", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/lambda-04-templ-parm.C b/gcc/testsuite/g++.dg/coroutines/torture/lambda-04-templ-parm.C new file mode 100644 index 00000000000..adf31e22dba --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/lambda-04-templ-parm.C @@ -0,0 +1,47 @@ +// { dg-do run } +// { dg-additional-options "-std=c++2a" } + +// generic Lambda with template parm (from c++20) + +#include "../coro.h" + +// boiler-plate for tests of codegen +#define USE_AWAIT_TRANSFORM +#include "../coro1-ret-int-yield-int.h" + +int main () +{ + auto f = [](T y) -> coro1 + { + PRINT ("coro1: about to return"); + T x = y; + co_return co_await x + 3; + }; + + PRINT ("main: create coro1"); + coro1 x = f.operator()(17); + if (x.handle.done()) + abort(); + + x.handle.resume(); + PRINT ("main: after resume (initial suspend)"); + + x.handle.resume(); + PRINT ("main: after resume (co_await)"); + + /* Now we should have the co_returned value. */ + int y = x.handle.promise().get_value(); + if ( y != 20 ) + { + PRINTF ("main: wrong result (%d).", y); + abort (); + } + + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/lambda-05-capture-copy-local.C b/gcc/testsuite/g++.dg/coroutines/torture/lambda-05-capture-copy-local.C new file mode 100644 index 00000000000..7cd6648cca6 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/lambda-05-capture-copy-local.C @@ -0,0 +1,66 @@ +// { dg-do run } + +// lambda with parm and local state + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +int main () +{ + int local = 31; + + auto f = [=](int start) -> coro1 + { + int value = start; + PRINT ("f: about to yield start"); + co_yield start; + + value -= local; + PRINT ("f: about to yield (value-31)"); + co_yield value; + + value += 6163; + PRINT ("f: about to return (value+6163)"); + co_return value; + }; + + PRINT ("main: create coro1"); + coro1 x = f (42); + PRINT ("main: got coro1 - resuming (1)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (1)"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + PRINT ("main: apparently got 42 - resuming (2)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (2)"); + y = x.handle.promise().get_value(); + if ( y != 11 ) + abort (); + PRINT ("main: apparently got 11 - resuming (3)"); + if (x.handle.done()) + { + PRINT ("main: done?"); + abort(); + } + x.handle.resume(); + PRINT ("main: after resume (2) checking return"); + y = x.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + PRINT ("main: apparently got 6174"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/lambda-06-multi-capture.C b/gcc/testsuite/g++.dg/coroutines/torture/lambda-06-multi-capture.C new file mode 100644 index 00000000000..7b445d3d9cd --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/lambda-06-multi-capture.C @@ -0,0 +1,48 @@ +// { dg-do run } + +// lambda with parm and local state + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +int main () +{ + int a_copy = 20; + int a_ref = 10; + + auto f = [&, a_copy]() -> coro1 + { + co_return a_ref + a_copy; + }; + + { + coro1 A = f (); + A.handle.resume(); + PRINT ("main: [a_copy = 20, a_ref = 10]"); + + int y = A.handle.promise().get_value(); + if (y != 30) + { + PRINTF ("main: A co-ret = %d, should be 30\n", y); + abort (); + } + } + + a_copy = 5; + a_ref = 7; + + coro1 B = f (); + B.handle.resume(); + PRINT ("main: [a_copy = 5, a_ref = 7]"); + + int y = B.handle.promise().get_value(); + if (y != 27) + { + PRINTF ("main: B co-ret = %d, should be 27\n", y); + abort (); + } + + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/lambda-07-multi-yield.C b/gcc/testsuite/g++.dg/coroutines/torture/lambda-07-multi-yield.C new file mode 100644 index 00000000000..2bd58cbf2ec --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/lambda-07-multi-yield.C @@ -0,0 +1,46 @@ +// { dg-do run } + +// lambda with parm and local state + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +int main () +{ + int a_copy = 20; + int a_ref = 10; + + auto f = [&, a_copy]() -> coro1 + { + co_yield a_ref + a_copy; + co_return a_ref + a_copy; + }; + + coro1 A = f (); + A.handle.resume(); // Initial suspend. + PRINT ("main: [a_copy = 20, a_ref = 10]"); + + int y = A.handle.promise().get_value(); + if (y != 30) + { + PRINTF ("main: co-yield = %d, should be 30\n", y); + abort (); + } + + a_copy = 5; + a_ref = 7; + + A.handle.resume(); + PRINT ("main: [a_copy = 5, a_ref = 7]"); + + y = A.handle.promise().get_value(); + if (y != 27) + { + PRINTF ("main: co-ret = %d, should be 27\n", y); + abort (); + } + + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/lambda-08-co-ret-parm-ref.C b/gcc/testsuite/g++.dg/coroutines/torture/lambda-08-co-ret-parm-ref.C new file mode 100644 index 00000000000..4d5a44fe29a --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/lambda-08-co-ret-parm-ref.C @@ -0,0 +1,59 @@ +// { dg-do run } + +// Test that we can use a function param in a co_xxxx status. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +int main () +{ + int val; + + auto f = [&] (int x) -> coro1 + { + if (val + x > 25) + { + PRINT ("coro1: about to return k"); + co_return 6174; + } + else if (val + x > 20) + { + PRINTF ("coro1: about to co-return %d\n", val + x); + co_return val + x; + } + else if (val + x > 5) + { + PRINTF ("coro1: about to co-return %d\n", val); + co_return val; + } + else + { + PRINT ("coro1: about to return 0"); + co_return 0; + } + }; + + PRINT ("main: create coro1"); + + val = 20; // We should get this by ref. + int arg = 5; // and this as a regular parm. + + coro1 x = f (arg); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 25 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/local-var-0.C b/gcc/testsuite/g++.dg/coroutines/torture/local-var-0.C new file mode 100644 index 00000000000..a8956457dcd --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/local-var-0.C @@ -0,0 +1,37 @@ +// { dg-do run } + +// Simplest local decl. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f () noexcept +{ + const int answer = 42; + PRINTF ("coro1: about to return %d\n", answer); + co_return answer; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/local-var-1.C b/gcc/testsuite/g++.dg/coroutines/torture/local-var-1.C new file mode 100644 index 00000000000..69a5b707563 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/local-var-1.C @@ -0,0 +1,37 @@ +// { dg-do run } + +// Simplest local var + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f (int x) noexcept +{ + int answer = x + 6132; + PRINTF ("coro1: about to return %d\n", answer); + co_return answer; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (42); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/local-var-2.C b/gcc/testsuite/g++.dg/coroutines/torture/local-var-2.C new file mode 100644 index 00000000000..f232edabdae --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/local-var-2.C @@ -0,0 +1,50 @@ +// { dg-do run } + +// Test local vars in nested scopes + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f (int x) noexcept +{ + int y = x; + const int test = 20; + if (y > test) + { + int fred = y - 20; + PRINTF ("coro1: about to return %d\n", fred); + co_return fred; + } + else + { + PRINT ("coro1: about to return the answer\n"); + co_return y; + } + + co_return x; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (6194); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume"); + int y = x.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + //x.handle.resume(); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/local-var-3.C b/gcc/testsuite/g++.dg/coroutines/torture/local-var-3.C new file mode 100644 index 00000000000..bd06db53d48 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/local-var-3.C @@ -0,0 +1,65 @@ +// { dg-do run } + +// Test modifying a local var and yielding several instances of it. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f (int start) noexcept +{ + int value = start; + PRINT ("f: about to yield start"); + co_yield start; + + value -= 31; + PRINT ("f: about to yield (value-31)"); + co_yield value; + + value += 6163; + PRINT ("f: about to return (value+6163)"); + co_return value; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (42); + PRINT ("main: got coro1 - resuming (1)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (1)"); + int y = x.handle.promise().get_value(); + if ( y != 42 ) + abort (); + PRINT ("main: apparently got 42 - resuming (2)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (2)"); + y = x.handle.promise().get_value(); + if ( y != 11 ) + abort (); + PRINT ("main: apparently got 11 - resuming (3)"); + if (x.handle.done()) + { + PRINT ("main: done?"); + abort(); + } + x.handle.resume(); + PRINT ("main: after resume (2) checking return"); + y = x.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + PRINT ("main: apparently got 6174"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/local-var-4.C b/gcc/testsuite/g++.dg/coroutines/torture/local-var-4.C new file mode 100644 index 00000000000..419eb6b6467 --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/local-var-4.C @@ -0,0 +1,75 @@ +// { dg-do run } + +// Test modifying a local var across nested scopes containing vars +// hiding those at outer scopes. + +#include "../coro.h" + +// boiler-plate for tests of codegen +#include "../coro1-ret-int-yield-int.h" + +struct coro1 +f (int start) noexcept +{ + int value = start; + { + int value = start + 5; + { + int value = start + 20; + } + { + int value = start + 1; + PRINT ("f: about to yield start"); + co_yield value; + } + } + + value -= 31; + PRINT ("f: about to yield (value-31)"); + co_yield value; + + value += 6163; + PRINT ("f: about to return (value+6163)"); + co_return value; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (42); + PRINT ("main: got coro1 - resuming (1)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (1)"); + int y = x.handle.promise().get_value(); + if ( y != 43 ) + abort (); + PRINT ("main: apparently got 42 - resuming (2)"); + if (x.handle.done()) + abort(); + x.handle.resume(); + PRINT ("main: after resume (2)"); + y = x.handle.promise().get_value(); + if ( y != 11 ) + abort (); + PRINT ("main: apparently got 11 - resuming (3)"); + if (x.handle.done()) + { + PRINT ("main: done?"); + abort(); + } + x.handle.resume(); + PRINT ("main: after resume (2) checking return"); + y = x.handle.promise().get_value(); + if ( y != 6174 ) + abort (); + PRINT ("main: apparently got 6174"); + if (!x.handle.done()) + { + PRINT ("main: apparently not done..."); + abort (); + } + PRINT ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/mid-suspend-destruction-0.C b/gcc/testsuite/g++.dg/coroutines/torture/mid-suspend-destruction-0.C new file mode 100644 index 00000000000..934fb19de7d --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/mid-suspend-destruction-0.C @@ -0,0 +1,107 @@ +// { dg-do run } +// { dg-output "main: returning\n" } +// { dg-output "Destroyed coro1\n" } +// { dg-output "Destroyed suspend_always_prt\n" } +// { dg-output "Destroyed Promise\n" } + +// Check that we still get the right DTORs run when we let a suspended coro +// go out of scope. + +#include "../coro.h" + +struct coro1 { + struct promise_type; + using handle_type = coro::coroutine_handle; + handle_type handle; + coro1 () : handle(0) {} + coro1 (handle_type _handle) + : handle(_handle) { + PRINT("Created coro1 object from handle"); + } + coro1 (const coro1 &) = delete; // no copying + coro1 (coro1 &&s) : handle(s.handle) { + s.handle = nullptr; + PRINT("coro1 mv ctor "); + } + coro1 &operator = (coro1 &&s) { + handle = s.handle; + s.handle = nullptr; + PRINT("coro1 op= "); + return *this; + } + ~coro1() { + printf ("Destroyed coro1\n"); + if ( handle ) + handle.destroy(); + } + + struct suspend_never_prt { + bool await_ready() const noexcept { return true; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-never-susp");} + void await_resume() const noexcept { PRINT ("susp-never-resume");} + ~suspend_never_prt() {}; + }; + + struct suspend_always_prt { + bool await_ready() const noexcept { return false; } + void await_suspend(handle_type) const noexcept { PRINT ("susp-always-susp");} + void await_resume() const noexcept { PRINT ("susp-always-resume");} + ~suspend_always_prt() { printf ("Destroyed suspend_always_prt\n"); } + }; + + struct promise_type { + promise_type() { PRINT ("Created Promise"); } + ~promise_type() { printf ("Destroyed Promise\n"); } + + auto get_return_object () { + PRINT ("get_return_object: handle from promise"); + return handle_type::from_promise (*this); + } + auto initial_suspend () { + PRINT ("get initial_suspend (always)"); + return suspend_always_prt{}; + } + auto final_suspend () { + PRINT ("get final_suspend (always)"); + return suspend_always_prt{}; + } + void return_void () { + PRINT ("return_void ()"); + } + + void unhandled_exception() { PRINT ("** unhandled exception"); } + }; +}; + +struct coro1 +f () noexcept +{ + PRINT ("coro1: about to return"); + co_return; +} + +int main () +{ + PRINT ("main: create coro1"); + struct coro1 x = f (); + PRINT ("main: got coro1 - resuming"); + if (x.handle.done()) + { + PRINT ("main: f() should be suspended, says it's done"); + abort(); + } + +#if __has_builtin (__builtin_coro_suspended) + if (! __builtin_coro_suspended(handle)) + { + PRINT ("main: f() should be suspended, but says it isn't"); + abort(); + } +#endif + + /* We are suspended... so let everything out of scope and therefore + destroy it. */ + + puts ("main: returning"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr92933.C b/gcc/testsuite/g++.dg/coroutines/torture/pr92933.C new file mode 100644 index 00000000000..b2f1be78b3e --- /dev/null +++ b/gcc/testsuite/g++.dg/coroutines/torture/pr92933.C @@ -0,0 +1,18 @@ +// { dg-do compile } + +// Test that we compile the simple case described in PR 92933 + +#include "../coro.h" + +#define RETURN_VOID +#include "../coro1-ret-int-yield-int.h" + +struct some_error {}; + +coro1 +foo() { + try { + co_return; + } catch (some_error) { + } +} diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h index b6abeab751e..a1207a20a3c 100644 --- a/gcc/tree-pass.h +++ b/gcc/tree-pass.h @@ -478,6 +478,8 @@ extern gimple_opt_pass *make_pass_gen_hsail (gcc::context *ctxt); extern gimple_opt_pass *make_pass_warn_nonnull_compare (gcc::context *ctxt); extern gimple_opt_pass *make_pass_sprintf_length (gcc::context *ctxt); extern gimple_opt_pass *make_pass_walloca (gcc::context *ctxt); +extern gimple_opt_pass *make_pass_coroutine_lower_builtins (gcc::context *ctxt); +extern gimple_opt_pass *make_pass_coroutine_early_expand_ifns (gcc::context *ctxt); /* IPA Passes */ extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt); diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index f0d3b1c140f..79bab714419 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,3 +1,9 @@ +2020-01-18 Iain Sandoe + + * include/Makefile.am: Add coroutine to the std set. + * include/Makefile.in: Regenerated. + * include/std/coroutine: New file. + 2020-01-17 Jonathan Wakely PR libstdc++/92376 diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am index b38defcafb2..ad4404793be 100644 --- a/libstdc++-v3/include/Makefile.am +++ b/libstdc++-v3/include/Makefile.am @@ -38,6 +38,7 @@ std_headers = \ ${std_srcdir}/complex \ ${std_srcdir}/concepts \ ${std_srcdir}/condition_variable \ + ${std_srcdir}/coroutine \ ${std_srcdir}/deque \ ${std_srcdir}/execution \ ${std_srcdir}/filesystem \ diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in index ae4a493ea65..f8b56452242 100644 --- a/libstdc++-v3/include/Makefile.in +++ b/libstdc++-v3/include/Makefile.in @@ -382,6 +382,7 @@ std_headers = \ ${std_srcdir}/complex \ ${std_srcdir}/concepts \ ${std_srcdir}/condition_variable \ + ${std_srcdir}/coroutine \ ${std_srcdir}/deque \ ${std_srcdir}/execution \ ${std_srcdir}/filesystem \ diff --git a/libstdc++-v3/include/std/coroutine b/libstdc++-v3/include/std/coroutine new file mode 100644 index 00000000000..363402330e4 --- /dev/null +++ b/libstdc++-v3/include/std/coroutine @@ -0,0 +1,291 @@ +// -*- C++ -*- + +// Copyright (C) 2019-2020 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +/** @file include/coroutine + * This is a Standard C++ Library header. + */ + +#ifndef _GLIBCXX_COROUTINE +#define _GLIBCXX_COROUTINE 1 + +#pragma GCC system_header + +// It is very likely that earlier versions would work, but they are untested. +#if __cplusplus >= 201402L + +#include + +/** + * @defgroup coroutines Coroutines + * + * Components for supporting coroutine implementations. + */ + +#if __cplusplus > 201703L && __cpp_impl_three_way_comparison >= 201907L +# include +# define _COROUTINES_USE_SPACESHIP 1 +#else +# include // for std::less +# define _COROUTINES_USE_SPACESHIP 0 +#endif + +namespace std _GLIBCXX_VISIBILITY (default) +{ + _GLIBCXX_BEGIN_NAMESPACE_VERSION + +#if __cpp_coroutines + inline namespace __n4835 { + + // 17.12.2 coroutine traits + /// [coroutine.traits] + /// [coroutine.traits.primary] + template + struct coroutine_traits + { + using promise_type = typename _Result::promise_type; + }; + + // 17.12.3 Class template coroutine_handle + /// [coroutine.handle] + template + struct coroutine_handle; + + template <> struct + coroutine_handle + { + public: + // 17.12.3.1, construct/reset + constexpr coroutine_handle() noexcept : _M_fr_ptr(0) {} + + constexpr coroutine_handle(std::nullptr_t __h) noexcept + : _M_fr_ptr(__h) + {} + + coroutine_handle& operator=(std::nullptr_t) noexcept + { + _M_fr_ptr = nullptr; + return *this; + } + + public: + // 17.12.3.2, export/import + constexpr void* address() const noexcept { return _M_fr_ptr; } + + constexpr static coroutine_handle from_address(void* __a) noexcept + { + coroutine_handle __self; + __self._M_fr_ptr = __a; + return __self; + } + + public: + // 17.12.3.3, observers + constexpr explicit operator bool() const noexcept + { + return bool(_M_fr_ptr); + } + + bool done() const noexcept { return __builtin_coro_done(_M_fr_ptr); } + + // 17.12.3.4, resumption + void operator()() const { resume(); } + + void resume() const { __builtin_coro_resume(_M_fr_ptr); } + + void destroy() const { __builtin_coro_destroy(_M_fr_ptr); } + + protected: + void* _M_fr_ptr; + }; + + // 17.12.3.6 Comparison operators + /// [coroutine.handle.compare] + constexpr bool operator==(coroutine_handle<> __a, + coroutine_handle<> __b) noexcept + { + return __a.address() == __b.address(); + } + +#if _COROUTINES_USE_SPACESHIP + constexpr strong_ordering + operator<=>(coroutine_handle<> __a, coroutine_handle<> __b) noexcept + { return std::compare_three_way()(__a.address(), __b.address()); } +#else + // These are to enable operation with std=c++14,17. + constexpr bool operator!=(coroutine_handle<> __a, + coroutine_handle<> __b) noexcept + { + return !(__a == __b); + } + + constexpr bool operator<(coroutine_handle<> __a, + coroutine_handle<> __b) noexcept + { + return less()(__a.address(), __b.address()); + } + + constexpr bool operator>(coroutine_handle<> __a, + coroutine_handle<> __b) noexcept + { + return __b < __a; + } + + constexpr bool operator<=(coroutine_handle<> __a, + coroutine_handle<> __b) noexcept + { + return !(__a > __b); + } + + constexpr bool operator>=(coroutine_handle<> __a, + coroutine_handle<> __b) noexcept + { + return !(__a < __b); + } +#endif + + template + struct coroutine_handle : coroutine_handle<> + { + // 17.12.3.1, construct/reset + using coroutine_handle<>::coroutine_handle; + + static coroutine_handle from_promise(_Promise& p) + { + coroutine_handle __self; + __self._M_fr_ptr + = __builtin_coro_promise((char*) &p, __alignof(_Promise), true); + return __self; + } + + coroutine_handle& operator=(std::nullptr_t) noexcept + { + coroutine_handle<>::operator=(nullptr); + return *this; + } + + // 17.12.3.2, export/import + constexpr static coroutine_handle from_address(void* __a) + { + coroutine_handle __self; + __self._M_fr_ptr = __a; + return __self; + } + + // 17.12.3.5, promise accesss + _Promise& promise() const + { + void* __t + = __builtin_coro_promise (this->_M_fr_ptr, __alignof(_Promise), false); + return *static_cast<_Promise*>(__t); + } + }; + + /// [coroutine.noop] + struct noop_coroutine_promise + { + }; + + void __dummy_resume_destroy() __attribute__((__weak__)); + void __dummy_resume_destroy() {} + + struct __noop_coro_frame + { + void (*__r)() = __dummy_resume_destroy; + void (*__d)() = __dummy_resume_destroy; + struct noop_coroutine_promise __p; + } __noop_coro_fr __attribute__((__weak__)); + + // 17.12.4.1 Class noop_coroutine_promise + /// [coroutine.promise.noop] + template <> + struct coroutine_handle : public coroutine_handle<> + { + using _Promise = noop_coroutine_promise; + + public: + // 17.12.4.2.1, observers + constexpr explicit operator bool() const noexcept { return true; } + + constexpr bool done() const noexcept { return false; } + + // 17.12.4.2.2, resumption + void operator()() const noexcept {} + + void resume() const noexcept {} + + void destroy() const noexcept {} + + // 17.12.4.2.3, promise access + _Promise& promise() const + { + return *static_cast<_Promise*>( + __builtin_coro_promise(this->_M_fr_ptr, __alignof(_Promise), false)); + } + + // 17.12.4.2.4, address + private: + friend coroutine_handle noop_coroutine() noexcept; + + coroutine_handle() noexcept { this->_M_fr_ptr = (void*) &__noop_coro_fr; } + }; + + using noop_coroutine_handle = coroutine_handle; + + inline noop_coroutine_handle noop_coroutine() noexcept + { + return noop_coroutine_handle(); + } + + // 17.12.5 Trivial awaitables + /// [coroutine.trivial.awaitables] + struct suspend_always + { + bool await_ready() { return false; } + + void await_suspend(coroutine_handle<>) {} + + void await_resume() {} + }; + + struct suspend_never + { + bool await_ready() { return true; } + + void await_suspend(coroutine_handle<>) {} + + void await_resume() {} + }; + + } // namespace __n4835 + +#else +#error "the coroutine header requires -fcoroutines" +#endif + + _GLIBCXX_END_NAMESPACE_VERSION +} // namespace std + +#endif // C++14 (we are allowing use from at least this) + +#endif // _GLIBCXX_COROUTINE