Hi, all! I decided to start working on coroutines support in C++ (according to P0057R2 proposal: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0057r2.pdf).
I did some analysis of how other compilers implement coroutines: https://github.com/miyuki-chan/coroutines This github repo has a small example project for Microsoft Visual Studio and also the relevant part of assembly code generated by MSVC. It also has a link to Clang testcase for coroutines semantic analysis and diagnostics, as well as the actual diagnostic output of Clang for that testcase. In the attached patch I started to work on parsing and semantic analysis. For now it can parse some basic uses of co_await, co_yield and co_return and diagnose some erroneous cases. No support for templates, and no 'for co_await' yet. My main question is: what is the correct way of constructing the AST? I started looking at how range-based for loops are implemented. As far as I understand, GCC converts range-based for into normal for loop immediately, when possible, i.e. when there are no dependent types involved. But when parsing template definition, GCC may create a node for range-based for loop and convert it during instantiation. Should we do the same for co_await/co_yield/co_return? Or would it be better to always construct just AST nodes (perhaps with some meta-information) and delay actual codegen until we start genericising the function (apparently, Clang works this way)? Just it case, what I mean by codegen. The proposal says: "Let e be the operand of the yield-expression and p be an lvalue naming the promise object of the enclosing coroutine (8.4.4), then the yield-expression is equivalent to the expression co_await p.yield_value(e)." So, when we parse "co_yield e", when should we build "co_await p.yield_value(e)" from it and further expand co_await? -- Regards, Mikhail Maltsev
diff --git a/.gitignore b/.gitignore index c9a6158..4595d5e 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,9 @@ TAGS.sub .clang-format +.agignore +.ycm_extra_conf.py + .gdbinit .gdb_history diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c index 22ea7da..9962ab5 100644 --- a/gcc/c-family/c-common.c +++ b/gcc/c-family/c-common.c @@ -598,6 +598,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 }, + /* C++ coroutines */ + { "co_await", RID_CO_AWAIT, D_CXX_COROUTINES_FLAGS | D_CXXWARN }, + { "co_return", RID_CO_RETURN, D_CXX_COROUTINES_FLAGS | D_CXXWARN }, + { "co_yield", RID_CO_YIELD, 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 fa3746c..4f753c9 100644 --- a/gcc/c-family/c-common.h +++ b/gcc/c-family/c-common.h @@ -154,6 +154,9 @@ enum rid /* C++ concepts */ RID_CONCEPT, RID_REQUIRES, + /* C++ coroutines */ + RID_CO_AWAIT, RID_CO_RETURN, RID_CO_YIELD, + /* C++ transactional memory. */ RID_ATOMIC_NOEXCEPT, RID_ATOMIC_CANCEL, RID_SYNCHRONIZED, @@ -383,20 +386,22 @@ extern machine_mode c_default_pointer_mode; mask) is _true_. Thus for keywords which are present in all languages the disable field is zero. */ -#define D_CONLY 0x001 /* C only (not in C++). */ -#define D_CXXONLY 0x002 /* C++ only (not in C). */ -#define D_C99 0x004 /* In C, C99 only. */ -#define D_CXX11 0x008 /* In C++, C++11 only. */ -#define D_EXT 0x010 /* GCC extension. */ -#define D_EXT89 0x020 /* GCC extension incorporated in C99. */ -#define D_ASM 0x040 /* Disabled by -fno-asm. */ -#define D_OBJC 0x080 /* In Objective C and neither C nor C++. */ -#define D_CXX_OBJC 0x100 /* In Objective C, and C++, but not C. */ -#define D_CXXWARN 0x200 /* In C warn with -Wcxx-compat. */ -#define D_CXX_CONCEPTS 0x400 /* In C++, only with concepts. */ -#define D_TRANSMEM 0X800 /* C++ transactional memory TS. */ +#define D_CONLY 0x0001 /* C only (not in C++). */ +#define D_CXXONLY 0x0002 /* C++ only (not in C). */ +#define D_C99 0x0004 /* In C, C99 only. */ +#define D_CXX11 0x0008 /* In C++, C++11 only. */ +#define D_EXT 0x0010 /* GCC extension. */ +#define D_EXT89 0x0020 /* GCC extension incorporated in C99. */ +#define D_ASM 0x0040 /* Disabled by -fno-asm. */ +#define D_OBJC 0x0080 /* In Objective C and neither C nor C++. */ +#define D_CXX_OBJC 0x0100 /* In Objective C, and C++, but not C. */ +#define D_CXXWARN 0x0200 /* In C warn with -Wcxx-compat. */ +#define D_CXX_CONCEPTS 0x0400 /* In C++, only with concepts. */ +#define D_TRANSMEM 0x0800 /* C++ transactional memory TS. */ +#define D_CXX_COROUTINES 0x1000 /* C++ coroutines TS. */ #define D_CXX_CONCEPTS_FLAGS D_CXXONLY | D_CXX_CONCEPTS +#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 19999c7..0956d2f 100644 --- a/gcc/c-family/c-cppbuiltin.c +++ b/gcc/c-family/c-cppbuiltin.c @@ -882,6 +882,8 @@ c_cpp_builtins (cpp_reader *pfile) /* Use a value smaller than the 201505 specified in the TS, since we don't yet support atomic_cancel. */ cpp_define (pfile, "__cpp_transactional_memory=210500"); + if (flag_coroutines) + cpp_define (pfile, "__cpp_coroutines=1"); if (flag_sized_deallocation) cpp_define (pfile, "__cpp_sized_deallocation=201309"); } diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 7c5f6c7..c3e9070 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1142,6 +1142,10 @@ fconcepts C++ ObjC++ Var(flag_concepts) Enable support for C++ concepts. +fcoroutines +C++ ObjC++ Var(flag_coroutines) +Enable support for C++ coroutines. + fcond-mismatch C ObjC C++ ObjC++ Allow the arguments of the '?' operator to have different types. diff --git a/gcc/cp/Make-lang.in b/gcc/cp/Make-lang.in index 2286c64..4c490aa 100644 --- a/gcc/cp/Make-lang.in +++ b/gcc/cp/Make-lang.in @@ -79,7 +79,9 @@ CXX_AND_OBJCXX_OBJS = cp/call.o cp/decl.o cp/expr.o cp/pt.o cp/typeck2.o \ cp/cp-cilkplus.o \ cp/cp-gimplify.o cp/cp-array-notation.o cp/lambda.o \ cp/vtable-class-hierarchy.o cp/constexpr.o cp/cp-ubsan.o \ - cp/constraint.o cp/logic.o $(CXX_C_OBJS) + cp/constraint.o cp/logic.o \ + cp/coroutine.o \ + $(CXX_C_OBJS) # Language-specific object files for C++. CXX_OBJS = cp/cp-lang.o c-family/stub-objc.o $(CXX_AND_OBJCXX_OBJS) diff --git a/gcc/cp/coroutine.cc b/gcc/cp/coroutine.cc new file mode 100644 index 0000000..18e2fe2 --- /dev/null +++ b/gcc/cp/coroutine.cc @@ -0,0 +1,528 @@ +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "hash-set.h" +#include "machmode.h" +#include "vec.h" +#include "double-int.h" +#include "input.h" +#include "alias.h" +#include "symtab.h" +#include "wide-int.h" +#include "inchash.h" +#include "tree.h" +#include "stringpool.h" +#include "attribs.h" +#include "intl.h" +#include "flags.h" +#include "cp-tree.h" +#include "c-family/c-common.h" +#include "c-family/c-objc.h" +#include "cp-objcp-common.h" +#include "tree-inline.h" +#include "decl.h" +#include "toplev.h" +#include "type-utils.h" +#include "print-tree.h" + +// Data associated with a coroutine. + +struct coroutine_data // ??? GTY +{ + // Location of the first keyword, indicating that this function is a + // coroutine. + location_t loc; + // That keyword (one of "co_yield", "co_return", "co_await") + const char *keyword; + // Coroutine promise object. + tree promise; + // Coroutine handle. + tree handle; + // The final_suspend_label (as defined in the standard) label. + tree final_suspend_label; +}; + +// Coroutine data of all coroutines in current translation unit. +// FIXME: avoid another global. + +static hash_map<function *, coroutine_data> *coroutines; + +#if 0 +static tree +build_await_temp (tree awaitable) +{ + /* Find out the type deduced by the declaration + `auto &&__awaitable = awaitable'. */ + tree awaitable_type = cp_build_reference_type (make_auto (), true); + awaitable_type = do_auto_deduction (awaitable_type, awaitable, + type_uses_auto (awaitable_type)); + + /* Create the __awaitable variable. */ + tree awaitable_temp = build_decl (input_location, VAR_DECL, + get_identifier ("__awaitable"), + awaitable_type); + TREE_USED (awaitable_temp) = 1; + DECL_ARTIFICIAL (awaitable_temp) = 1; + + return awaitable_temp; +} +#endif + +static inline tree +lookup_member_fn (tree awaitable_type, tree identifier, tsubst_flags_t complain) +{ + return lookup_member (awaitable_type, identifier, + /*protect=*/2, /*want_type=*/false, complain); +} + +static inline tree +build_member_call (tree awaitable, tree ident, vec<tree, va_gc> *args, + tsubst_flags_t complain) +{ + tree member = finish_class_member_access_expr (awaitable, ident, + false, complain); + if (member == error_mark_node) + return error_mark_node; + + return finish_call_expr (member, &args, + /*disallow_virtual=*/false, + /*koenig_p=*/false, + complain); +} + +static tree +build_await_expression_1 (tree promise, location_t loc, tree awaitable, + bool yield_p, tsubst_flags_t complain) +{ + (void)promise; + const char *op_name = yield_p ? "co_yield" : "co_await"; + + tree id_ready = get_identifier ("await_ready"), + id_suspend = get_identifier ("await_suspend"), + id_resume = get_identifier ("await_resume"); + + tree awaitable_type = TREE_TYPE (awaitable); + + if (!COMPLETE_TYPE_P (complete_type (awaitable_type))) + { + if (complain & tf_error) + error_at (loc, "%<%s%> operand of type %qT has incomplete type", + op_name, awaitable_type); + return error_mark_node; + } + + if (!CLASS_TYPE_P (awaitable_type)) + { + if (complain & tf_error) + error_at (loc, "%<%s%> operand of type %qT is not a class", + op_name, awaitable_type); + return error_mark_node; + } + + // create a scope for temporary + // tree stmt = begin_compound_statement (/*flags=*/0); + + // TODO: build a temporary, if the awaitable is a prvalue + tree awaitable_lv = awaitable; + + tree coro_handle = integer_one_node; + + /* First, lookup await_ready, await_suspend and await_resume (for + diagnostics). */ + tree memb_ready = lookup_member_fn (awaitable_type, id_ready, complain); + tree memb_suspend = lookup_member_fn (awaitable_type, id_suspend, complain); + tree memb_resume = lookup_member_fn (awaitable_type, id_resume, complain); + + if (!memb_ready || !memb_suspend || !memb_resume) + { + if (!(complain & tf_error)) + return error_mark_node; + + // If all three are missing, output a single error + if (!memb_ready && !memb_suspend && !memb_resume) + { + error_at (loc, "%<%s%> operand of type %qT has no " + "%<await_ready%>, %<await_suspend%> and " + "%<await_resume%> members", op_name, awaitable_type); + return error_mark_node; + } + + // Otherwise, output an error message for each missing function + if (!memb_ready) + error_at (loc, "%<%s%> operand of type %qT has no " + "%<await_ready%> member", op_name, awaitable_type); + if (!memb_suspend) + error_at (loc, "%<%s%> operand of type %qT has no " + "%<await_suspend%> member", op_name, awaitable_type); + if (!memb_resume) + error_at (loc, "%<%s%> operand of type %qT has no " + "%<await_resume%> member", op_name, awaitable_type); + return error_mark_node; + } + + vec<tree, va_gc> *args = make_tree_vector (); + + /* - await-ready is the expression e.await_ready(), contextually converted + to bool. */ + tree ready_expr = build_member_call (awaitable_lv, id_ready, args, complain); + // â await-resume is the expression e.await_resume(). + tree resume_expr = build_member_call (awaitable_lv, id_resume, args, + complain); + + // - await-suspend is the expression e.await_suspend(h)... + vec_safe_push (args, coro_handle); + tree suspend_expr = build_member_call (awaitable_lv, id_suspend, args, + complain); + + release_tree_vector (args); + + if (ready_expr == error_mark_node + || suspend_expr == error_mark_node + || resume_expr == error_mark_node) + return error_mark_node; + + /* - await-suspend is the expression e.await_suspend(h), which shall be a + prvalue of type void or bool. */ + if (TREE_TYPE (suspend_expr) != void_type_node + && TREE_TYPE (suspend_expr) != boolean_type_node) + { + if (complain & tf_error) + error_at (loc, "%<await_suspend%> must return %<bool%> or %<void%> " + " (got %qT)", TREE_TYPE (suspend_expr)); + return error_mark_node; + } + + /* We will return a conditional expression : + (condition ? ready_expr : else_expr) */ + tree else_expr, condition; + tree suspend_type = cv_unqualified (TREE_TYPE (suspend_expr)); + if (suspend_type == void_type_node) + { + /* If the type of await-suspend-expr is cv void, then await-keyword + cast-expression is equivalent to: + + ( + await-ready-expr ? await-resume-expr + : (await-suspend-expr, suspend-resume-point, + await-resume-expr) + ) */ + condition = ready_expr; + else_expr = cp_build_compound_expr (suspend_expr, resume_expr, complain); + } + else + { + /* otherwise, it is equivalent to: + ( + (await-ready-expr && !await-suspend-expr) + ? await-resume-expr + : (suspend-resume-point, await-resume-expr) + ) */ + tree not_suspend = cp_build_unary_op (TRUTH_NOT_EXPR, + suspend_expr, /*noconvert=*/false, + complain); + if (not_suspend == error_mark_node) + return error_mark_node; + + condition = build_x_binary_op (loc, TRUTH_AND_EXPR, + ready_expr, ERROR_MARK, + not_suspend, ERROR_MARK, + /*overload=*/NULL, complain); + else_expr = resume_expr; + } + + if (else_expr == error_mark_node || condition == error_mark_node) + return error_mark_node; + + return build_conditional_expr (loc, ready_expr, resume_expr, + else_expr, complain); +} + +/* Make function FUN a coroutine and output diagnostics at location LOC in case + of error. KW is one of "co_await", "co_yield" or "co_return" - the + keyword, which suggests, that current function is a coroutine (used in + diagnostics). Return true, if succeeded, or if FUN is already a coroutine. + Return false, if failed. */ + +static bool +maybe_convert_function_to_coroutine (function *fun, location_t loc, + const char *kw) +{ + if (fun->is_coroutine) + return true; + + tree fun_decl = fun->decl; + + /* 3.6.1 Main function + ... + The function main shall not be a coroutine (8.4.4). + ... */ + if (DECL_MAIN_P (fun_decl)) + { + error_at (loc, "%<%s%> cannot be used in function %<main%>", kw); + return false; + } + + /* 7.1.5 The constexpr specifier + The definition of a constexpr function shall satisfy the following + constraints: + ... + â it shall not be a coroutine (8.4.4); */ + if (DECL_DECLARED_CONSTEXPR_P (fun_decl)) + { + error_at (loc, "%<%s%> cannot be used in a constexpr function", kw); + return false; + } + + if (DECL_CONSTRUCTOR_P (fun_decl)) + { + error_at (loc, "%<%s%> cannot be used in a constructor", kw); + return false; + } + + if (DECL_DESTRUCTOR_P (fun_decl)) + { + error_at (loc, "%<%s%> cannot be used in a destructor", kw); + return false; + } + + if (varargs_function_p (fun_decl)) + { + error_at (loc, "%<%s%> cannot be used in a varargs function", kw); + return false; + } + + tree std_coro_traits + = namespace_binding (get_identifier ("coroutine_traits"), std_node); + if (!std_coro_traits || !DECL_CLASS_TEMPLATE_P (std_coro_traits)) + { + error_at (loc, + "you need to include <coroutine> before defining a coroutine"); + return false; + } + + tree first_parm = DECL_ARGUMENTS (fun->decl); + size_t num_parms = 0; + for (tree parm = first_parm; parm; parm = TREE_CHAIN (parm), num_parms++) + ; + + /* For a coroutine f that is a non-static member function, let P1 denote the + type of the implicit object parameter (13.3.1) and P2 ... Pn be the types + of the function parameters; otherwise let P1 ... Pn be the types of the + function parameters. Let R be the return type and F be the function-body + of f, T be the type std::coroutine_traits<R,P1,...,Pn> */ + + tree argvec = make_tree_vec (num_parms + 1); + TREE_VEC_ELT (argvec, 0) = TREE_TYPE (DECL_RESULT (fun->decl)); + + size_t ind = 1; + for (tree parm = first_parm; parm; parm = TREE_CHAIN (parm), ind++) + TREE_VEC_ELT (argvec, ind) = TREE_TYPE (parm); + + tree coro_traits = lookup_template_class (std_coro_traits, argvec, NULL_TREE, + NULL_TREE, 0, tf_warning_or_error); + tree promise_type_decl + = lookup_member (coro_traits, get_identifier ("promise_type"), + /*protect=*/2, /*want_type=*/true, tf_warning_or_error); + if (!promise_type_decl) + { + error_at (loc, "this function cannot be a coroutine: %qT has no " + "member named %<promise_type%>", + coro_traits); + return false; + } + + tree promise_type = TREE_TYPE (promise_type_decl); + + if (!COMPLETE_TYPE_P (complete_type (promise_type))) + { + error_at (loc, "this function cannot be a coroutine: %qT " + "has incomplete type", + promise_type); + return false; + } + + if (!CLASS_TYPE_P (promise_type)) + { + error_at (loc, "this function cannot be a coroutine: %qT is not a class", + promise_type); + return false; + } + + // Create the __coro_promise variable + tree promise = build_decl (input_location, VAR_DECL, + get_identifier ("__coro_promise"), promise_type); + TREE_USED (promise) = 1; + DECL_ARTIFICIAL (promise) = 1; + + vec<tree, va_gc> *empty_args = make_tree_vector (); + + tree initial_suspend + = build_member_call (promise, get_identifier ("initial_suspend"), + empty_args, tf_warning_or_error); + tree final_suspend + = build_member_call (promise, get_identifier ("final_suspend"), empty_args, + tf_warning_or_error); + if (initial_suspend == error_mark_node || final_suspend == error_mark_node) + return false; + + tree initial_await + = build_await_expression_1 (promise, loc, initial_suspend, + /*yield_p=*/false, tf_warning_or_error); + tree final_await + = build_await_expression_1 (promise, loc, final_suspend, + /*yield_p=*/false, tf_warning_or_error); + if (initial_await == error_mark_node || final_await == error_mark_node) + return false; + + if (!coroutines) + coroutines = new hash_map<function *, coroutine_data>; + + // Memoise promise object of the current coroutine. + coroutine_data coro_data = { loc, kw, promise, NULL, NULL }; + // It sucks, that we can't construct coro_data in-place. + coroutines->put (fun, coro_data); + + fun->is_coroutine = true; + return true; +} + +static inline tree +get_promise_object (function *fun) +{ + gcc_assert (fun->is_coroutine); + return coroutines->get (fun)->promise; +} + +/* Build an await-expression AWAITABLE is cast-expression, operand of co_await + operator. LOC is the location of the co_await token. COMPLAIN is a set of + flags which is used to suppress errors when performing template argument + deduction and substitution (for SFINAE). + Return the built expression. */ + +tree +build_await_expression (location_t loc, tree awaitable, tsubst_flags_t complain) +{ + gcc_checking_assert (!error_operand_p (awaitable)); + + function *fun = cfun; + gcc_assert (fun && fun->decl == current_function_decl); + + if (!maybe_convert_function_to_coroutine (fun, loc, "co_await")) + return error_mark_node; + + tree promise = get_promise_object (fun); + return build_await_expression_1 (promise, loc, awaitable, + /*yield_p=*/false, complain); +} + +/* Build a yield-expression. OPERAND is an assignment-expression or a + braced-init-list, the operand of co_yield. LOC is the location of co_yield + token. COMPLAIN plays the same role as in build_await_expression. + Return the built expression. */ + +tree +build_yield_expression (location_t loc, tree operand, tsubst_flags_t complain) +{ + function *fun = cfun; + + if (!maybe_convert_function_to_coroutine (fun, loc, "co_yield")) + return error_mark_node; + + /* Let e be the operand of the yield-expression and p be an lvalue naming the + promise object of the enclosing coroutine (8.4.4), then the + yield-expression is equivalent to the expression + + co_await p.yield_value(e) + + In this function PROMISE variable is 'p', and OPERAND parameter is 'e'. */ + + tree promise = get_promise_object (fun); + + vec<tree, va_gc> *args = make_tree_vector (); + vec_safe_push (args, operand); + tree awaitable = build_member_call (promise, get_identifier ("yield_value"), + args, complain); + release_tree_vector (args); + + if (awaitable == error_mark_node) + return error_mark_node; + + return build_await_expression_1 (promise, loc, awaitable, /*yield_p=*/true, + complain); +} + +/* Build a coroutine-return-statement. RET_VALUE is an expression or a + braced-init-list, the operand of co_return. NULL, if absent. LOC is the + location of co_return token. + Return the built statement. */ + +tree build_coroutine_return_stmt (location_t loc, tree ret_value, + tsubst_flags_t complain) +{ + function *fun = cfun; + + if (!maybe_convert_function_to_coroutine (fun, loc, "co_return")) + return error_mark_node; + + tree promise = get_promise_object (fun); + + /* The expression or braced-init-list of a co_return statement is called its + operand. Let p be an lvalue naming the coroutine promise object (8.4.4) + and P be the type of that object, then a co_return statement is equivalent + to: + + { S; goto final_suspend_label; } + + where f inal_suspend_label is as defined in 8.4.4 and S is an expression + defined as follows: + â S is p.return_value(braced-init-list), if the operand is a + braced-init-list; + â S is p.return_value(expression), if the operand is an expression of + non-void type; + â S is p.return_void(), otherwise; */ + + // Name of member function we are going to call + const char *mem_fn_name = ret_value ? "return_value" : "return_void"; + + vec<tree, va_gc> *args = make_tree_vector (); + if (ret_value) + vec_safe_push (args, ret_value); + tree call_result + = build_member_call (promise, get_identifier (mem_fn_name), args, complain); + release_tree_vector (args); + + if (call_result == error_mark_node) + return error_mark_node; + + // ... S shall be a prvalue of type void. + tree result_type = TREE_TYPE (call_result); + if (result_type != void_type_node) + { + if (complain & tf_error) + error_at (loc, "%<%s%> must return %<void%> (got %qT)", mem_fn_name, + result_type); + return error_mark_node; + } + + // TODO: build "goto final_suspend_label;" + return call_result; +} + +/* Output a diagnostic message, which says that return statement cannot be used + in coroutine. LOC is the location of "return" token. */ + +void diagnose_return_in_coroutine (location_t loc) +{ + function *fun = cfun; + gcc_assert (fun->is_coroutine); + + coroutine_data *coro_data = coroutines->get (fun); + rich_location richloc (line_table, loc); + richloc.add_fixit_replace (source_range::from_location (loc), "co_return"); + error_at_rich_loc (&richloc, "return statement not allowed in coroutine; " + "did you mean %<co_return%>?"); + inform (coro_data->loc, "function is a coroutine due to use of %<%s%> here", + coro_data->keyword); +} + diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def index e6e5139..d3dda02 100644 --- a/gcc/cp/cp-tree.def +++ b/gcc/cp/cp-tree.def @@ -582,6 +582,15 @@ DEFTREECODE (PARM_CONSTR, "parm_constr", tcc_expression, 2) DEFTREECODE (CONJ_CONSTR, "conj_constr", tcc_expression, 2) DEFTREECODE (DISJ_CONSTR, "disj_constr", tcc_expression, 2) +/** Coroutines TS extensions. */ + +/* await-expression. */ +DEFTREECODE (AWAIT_EXPR, "await_expr", tcc_expression, 1) +/* yield-expression. */ +DEFTREECODE (YIELD_EXPR, "yield_expr", tcc_expression, 1) +/* coroutine-return-statement. + CO_RETURN_STMT_EXPR has the return expression E. */ +DEFTREECODE (CO_RETURN_STMT, "co_return_stmt", tcc_statement, 1) /* Local variables: diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index b1dc23c..c6fac43 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -5177,6 +5177,8 @@ enum auto_deduction_context ? TYPE_TI_TEMPLATE (NODE) \ : TYPE_NAME (NODE)) +#define CO_RETURN_STMT_EXPR(NODE) TREE_OPERAND (CO_RETURN_STMT_CHECK (NODE), 0) + /* in lex.c */ extern void init_reswords (void); @@ -6893,6 +6895,15 @@ extern tree decompose_assumptions (tree); extern tree decompose_conclusions (tree); extern bool subsumes (tree, tree); +/* in coroutine.cc */ +extern tree build_await_expression (location_t, tree, + tsubst_flags_t); +extern tree build_yield_expression (location_t, tree, + tsubst_flags_t); +extern tree build_coroutine_return_stmt (location_t, tree, + tsubst_flags_t); +extern void diagnose_return_in_coroutine (location_t); + /* in vtable-class-hierarchy.c */ extern void vtv_compute_class_hierarchy_transitive_closure (void); extern void vtv_generate_init_routine (void); diff --git a/gcc/cp/cxx-pretty-print.c b/gcc/cp/cxx-pretty-print.c index cc28045..ca834a5 100644 --- a/gcc/cp/cxx-pretty-print.c +++ b/gcc/cp/cxx-pretty-print.c @@ -826,6 +826,12 @@ cxx_pretty_printer::unary_expression (tree t) pp_cxx_cast_expression (this, TREE_OPERAND (t, 0)); break; + case AWAIT_EXPR: + pp_cxx_ws_string (this, "co_await"); + pp_cxx_whitespace (this); + pp_cxx_cast_expression (this, TREE_OPERAND (t, 0)); + break; + default: c_pretty_printer::unary_expression (t); break; @@ -989,6 +995,10 @@ pp_cxx_assignment_operator (cxx_pretty_printer *pp, tree t) throw-expression: throw assignment-expression(opt) + yield-expression: + co_yield assignment-expression + co_yield braced-init-list + assignment-operator: one of = *= /= %= += -= >>= <<= &= ^= |= */ @@ -1018,6 +1028,11 @@ cxx_pretty_printer::assignment_expression (tree e) assignment_expression (TREE_OPERAND (e, 2)); break; + case YIELD_EXPR: + pp_cxx_ws_string (this, "co_yield"); + assignment_expression (TREE_OPERAND (e, 0)); + break; + default: conditional_expression (e); break; @@ -1099,6 +1114,7 @@ cxx_pretty_printer::expression (tree t) case SIZEOF_EXPR: case ALIGNOF_EXPR: case NOEXCEPT_EXPR: + case AWAIT_EXPR: unary_expression (t); break; @@ -2027,6 +2043,14 @@ cxx_pretty_printer::statement (tree t) pp_needs_newline (this) = true; break; + case CO_RETURN_STMT: + pp_cxx_ws_string (this, "co_return"); + if (CO_RETURN_STMT_EXPR (t)) + expression (CO_RETURN_STMT_EXPR (t)); + pp_cxx_semicolon (this); + pp_needs_newline (this) = true; + break; + /* expression-statement: expression(opt) ; */ case EXPR_STMT: diff --git a/gcc/cp/lex.c b/gcc/cp/lex.c index e69793e..1e16fb8 100644 --- a/gcc/cp/lex.c +++ b/gcc/cp/lex.c @@ -169,6 +169,8 @@ init_reswords (void) mask |= D_CXX11; if (!flag_concepts) mask |= D_CXX_CONCEPTS; + if (!flag_coroutines) + mask |= D_CXX_COROUTINES; if (!flag_tm) mask |= D_TRANSMEM; if (flag_no_asm) diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index 535052f..fab2268 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -2426,6 +2426,11 @@ static bool cp_parser_function_transaction static tree cp_parser_transaction_cancel (cp_parser *); +/* Coroutines Extensions */ + +static tree cp_parser_yield_expression + (cp_parser *); + enum pragma_context { pragma_external, pragma_member, @@ -2632,6 +2637,8 @@ static bool cp_parser_array_designator_p (cp_parser *); static bool cp_parser_skip_to_closing_square_bracket (cp_parser *); +static inline bool assignment_expr_delimiter_p + (cpp_ttype type); /* Concept-related syntactic transformations */ @@ -7640,6 +7647,7 @@ cp_parser_pseudo_destructor_name (cp_parser* parser, postfix-expression ++ cast-expression -- cast-expression + await-expression [Coroutines] unary-operator cast-expression sizeof unary-expression sizeof ( type-id ) @@ -7825,6 +7833,28 @@ cp_parser_unary_expression (cp_parser *parser, cp_id_kind * pidk, return finish_noexcept_expr (expr, tf_warning_or_error); } + case RID_CO_AWAIT: + { + /* await-expression: + co_await cast-expression */ + + /* FIXME: only warn for unevaluated */ + if (cp_noexcept_operand) + { + error_at (token->location, "co_await in noexcept"); + return error_mark_node; + } + + cp_lexer_consume_token (parser->lexer); + cp_expr operand = cp_parser_cast_expression (parser, + /*address_p=*/false, + /*cast_p=*/false, + /*decltype=*/false, + NULL); + return build_await_expression (token->location, operand, + tf_warning_or_error); + } + default: break; } @@ -9027,6 +9057,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. @@ -9043,6 +9074,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); + /* Likewise, the `co_yield' keyword denotes the beginning of + 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 @@ -10408,6 +10443,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: statement = cp_parser_jump_statement (parser); break; @@ -11695,6 +11731,7 @@ cp_parser_for_init_statement (cp_parser* parser, tree *decl) continue ; return expression [opt] ; return braced-init-list ; + coroutine-return-statement goto identifier ; GNU extension: @@ -11702,6 +11739,10 @@ cp_parser_for_init_statement (cp_parser* parser, tree *decl) jump-statement: goto * expression ; + coroutine-return-statement: + co_return expression [opt] ; + co_return braced-init-list ; + Returns the new BREAK_STMT, CONTINUE_STMT, RETURN_EXPR, or GOTO_EXPR. */ static tree @@ -11772,6 +11813,7 @@ cp_parser_jump_statement (cp_parser* parser) break; case RID_RETURN: + case RID_CO_RETURN: { tree expr; bool expr_non_constant_p; @@ -11788,8 +11830,24 @@ 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. */ - statement = finish_return_stmt (expr); + /* Build the return-statement or coroutine-return-statement. */ + if (keyword == RID_RETURN) + { + /* FIXME: this is a prototype. In reality we need to check after + parsing the whole function, because otherwise we will not + diagnose return statements before coroutine-related keywords. */ + if (coroutine_p (cfun)) + { + diagnose_return_in_coroutine (token->location); + statement = error_mark_node; + } + else + statement = finish_return_stmt (expr); + } + else + statement = build_coroutine_return_stmt (token->location, expr, + tf_warning_or_error); + /* Look for the final `;'. */ cp_parser_require (parser, CPP_SEMICOLON, RT_SEMICOLON); } @@ -23280,6 +23338,19 @@ cp_parser_exception_declaration (cp_parser* parser) return grokdeclarator (declarator, &type_specifiers, CATCHPARM, 1, NULL); } +/* Check whether a token of type TYPE terminates an assignment-expression. */ + +static inline bool +assignment_expr_delimiter_p (cpp_ttype type) +{ + return (type == CPP_COMMA + || type == CPP_SEMICOLON + || type == CPP_CLOSE_PAREN + || type == CPP_CLOSE_SQUARE + || type == CPP_CLOSE_BRACE + || type == CPP_COLON); +} + /* Parse a throw-expression. throw-expression: @@ -23297,12 +23368,7 @@ cp_parser_throw_expression (cp_parser* parser) token = cp_lexer_peek_token (parser->lexer); /* Figure out whether or not there is an assignment-expression following the "throw" keyword. */ - if (token->type == CPP_COMMA - || token->type == CPP_SEMICOLON - || token->type == CPP_CLOSE_PAREN - || token->type == CPP_CLOSE_SQUARE - || token->type == CPP_CLOSE_BRACE - || token->type == CPP_COLON) + if (assignment_expr_delimiter_p (token->type)) expression = NULL_TREE; else expression = cp_parser_assignment_expression (parser); @@ -23310,6 +23376,41 @@ cp_parser_throw_expression (cp_parser* parser) return build_throw (expression); } +/* Parse a yield-expression. + + yield-expression: + co_yield assignment-expression [opt] + co_yield braced-init-list + + Returns a THROW_EXPR representing the throw-expression. */ + +static tree +cp_parser_yield_expression (cp_parser *parser) +{ + // gcc_checking_assert (cp_lexer_next_token_is (parser->lexer, RID_CO_YIELD)); + + cp_token *co_yield_tok = cp_lexer_peek_token(parser->lexer); + location_t loc = co_yield_tok->location; + + // Consume co_yield. + cp_lexer_consume_token (parser->lexer); + + cp_token *token = cp_lexer_peek_token (parser->lexer); + tree expr; // Operand of co_yield + + // Select the right production. + if (token->type == CPP_OPEN_BRACE) + { + cp_lexer_set_source_position (parser->lexer); + expr = cp_parser_braced_list (parser, NULL); + } + else if (assignment_expr_delimiter_p (token->type)) + expr = NULL_TREE; + else + expr = cp_parser_assignment_expression (parser); + return build_yield_expression (loc, expr, tf_warning_or_error); +} + /* GNU Extensions */ /* Parse an (optional) asm-specification. diff --git a/gcc/function.h b/gcc/function.h index c4368cd..339a89e 100644 --- a/gcc/function.h +++ b/gcc/function.h @@ -378,6 +378,9 @@ struct GTY(()) function { /* Set when the tail call has been identified. */ unsigned int tail_call_marked : 1; + + /* Nonzero, if current function is a C++ coroutine (i.e. has side stack). */ + unsigned int is_coroutine : 1; }; /* Add the decl D to the local_decls list of FUN. */ @@ -448,6 +451,14 @@ set_loops_for_fn (struct function *fn, struct loops *loops) fn->x_current_loops = loops; } +/* Check, if FN is a coroutine. */ + +inline bool +coroutine_p (function *fn) +{ + return fn->is_coroutine; +} + /* For backward compatibility... eventually these should all go away. */ #define current_function_funcdef_no (cfun->funcdef_no) diff --git a/gcc/testsuite/g++.dg/cpp1z/coroutine1.C b/gcc/testsuite/g++.dg/cpp1z/coroutine1.C new file mode 100644 index 0000000..4b55dfd --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/coroutine1.C @@ -0,0 +1,144 @@ +// { dg-do compile { target c++14 } } +// { dg-options "-fcoroutines" } + +#ifndef __cpp_coroutines +# error "__cpp_coroutines" +// FIXME: update when correct value becomes known +#elif __cpp_coroutines != 1 +# error "__cpp_coroutines != 1" +#endif + +struct test_future +{ + bool await_ready (); + void await_suspend (int); + int await_resume (); +}; + +test_future f; + +void +no_coro_traits () +{ + co_await f; // { dg-error "you need to include" } +} + +namespace std { +template <typename... T> struct coroutine_traits; +} // namespace std + +void +no_promise_type () +{ + co_await f; // { dg-error "has no member named" } +} + +namespace std { +template <> +struct coroutine_traits<void, int> +{ + typedef int promise_type; +}; +} // namespace std + +void +promise_not_class (int) +{ + co_await f; // { dg-error "is not a class" } +} + +struct promise_fwd; + +namespace std { +template <> +struct coroutine_traits<void, short> +{ + using promise_type = ::promise_fwd; +}; +} // namespace std + +void +promise_incomplete (short) +{ + co_await f; // { dg-error "has incomplete type" } +} + +namespace std { +template <> +struct coroutine_traits<void, long> +{ + struct promise_type { }; +}; +} // namespace std + +void +promise_no_suspend (long) +{ + co_await f; // { dg-error "has no member named" } +} + +constexpr void +constexpr_coro () +{ + co_await f; // { dg-error "cannot be used in a constexpr function" } +} + +void +varargs_coro (int, ...) +{ + co_return; // { dg-error "cannot be used in a varargs function" } +} + +struct coro_cdtor +{ + coro_cdtor () + { + co_await f; // { dg-error "cannot be used in a constructor" } + } + ~coro_cdtor () + { + co_yield 1; // { dg-error "cannot be used in a destructor" } + } +}; + +namespace std { +template <> +struct coroutine_traits<double, double> +{ + struct promise_type + { + test_future initial_suspend (); + test_future final_suspend (); + void return_value (double); + }; +}; +} // namespace std + +double +return_in_coro (double) +{ + co_await f; // { dg-message "function is a coroutine" } + return 1; // { dg-error "return statement not allowed" } +} + +double +await_not_class (double) +{ + co_await 1; // { dg-error "is not a class" } +} + +struct bad_awaitable +{ + bool await_ready (); +}; + +double +bad_await (double) +{ + co_await bad_awaitable(); // { dg-error "has no" } +} + +int main () +{ + co_await f; // { dg-error "cannot be used" } +}