coroutines: Handle non-method promise expressions [PR95519]
authorIain Sandoe <iain@sandoe.co.uk>
Fri, 26 Jun 2020 09:48:35 +0000 (10:48 +0100)
committerIain Sandoe <iain@sandoe.co.uk>
Fri, 26 Jun 2020 11:40:16 +0000 (12:40 +0100)
The PR  points out that the standard does not restrict promise
expressions to methods, but the current implementation does.

The patch factors out the building of a general promise expression,
and then uses it in a fairly mechanical replacement of each case
that we need such an expressions.

This extends the handling for p.xxxxxx() expressions to cover the
cases where the promise member is some form callable.

Tests are added for each of the promise expressions.

It's somewhat tortuous to find good uses for this for the
get-return-object and get-return-object-on-allocation-failure
cases, but they are included anyway.

gcc/cp/ChangeLog:

PR c++/95519
* coroutines.cc (struct coroutine_info):Add a field
to hold computed p.return_void expressions.
(coro_build_promise_expression): New.
(get_coroutine_return_void_expr): New.
(finish_co_yield_expr): Build the promise expression
using coro_build_promise_expression.
(finish_co_return_stmt): Likewise.
(build_init_or_final_await): Likewise.
(morph_fn_to_coro): Likewise, for several cases.

gcc/testsuite/ChangeLog:

PR c++/95519
* g++.dg/coroutines/torture/pr95519-00-return_void.C: New test.
* g++.dg/coroutines/torture/pr95519-01-initial-suspend.C: New test.
* g++.dg/coroutines/torture/pr95519-02-final_suspend.C: New test.
* g++.dg/coroutines/torture/pr95519-03-return-value.C: New test.
* g++.dg/coroutines/torture/pr95519-04-yield-value.C: New test.
* g++.dg/coroutines/torture/pr95519-05-gro.C: New test.
* g++.dg/coroutines/torture/pr95519-06-grooaf.C: New test.
* g++.dg/coroutines/torture/pr95519-07-unhandled-exception.C: New test.

gcc/cp/coroutines.cc
gcc/testsuite/g++.dg/coroutines/torture/pr95519-00-return_void.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/pr95519-01-initial-suspend.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/pr95519-02-final_suspend.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/pr95519-03-return-value.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/pr95519-04-yield-value.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/pr95519-05-gro.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/pr95519-06-grooaf.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/torture/pr95519-07-unhandled-exception.C [new file with mode: 0644]

index ad27659223119966a656a0333bb4c6df1fbdaf4f..6e723c402db80e80e548f49969796e9ca95019ad 100644 (file)
@@ -88,6 +88,7 @@ struct GTY((for_user)) coroutine_info
                        one that will eventually be allocated in the coroutine
                        frame.  */
   tree promise_proxy; /* Likewise, a proxy promise instance.  */
+  tree return_void;   /* The expression for p.return_void() if it exists.  */
   location_t first_coro_keyword; /* The location of the keyword that made this
                                    function into a coroutine.  */
   /* Flags to avoid repeated errors for per-function issues.  */
@@ -554,6 +555,67 @@ lookup_promise_method (tree fndecl, tree member_id, location_t loc,
   return pm_memb;
 }
 
+/* Build an expression of the form p.method (args) where the p is a promise
+   object for the current coroutine.
+   OBJECT is the promise object instance to use, it may be NULL, in which case
+   we will use the promise_proxy instance for this coroutine.
+   ARGS may be NULL, for empty parm lists.  */
+
+static tree
+coro_build_promise_expression (tree fn, tree promise_obj, tree member_id,
+                              location_t loc, vec<tree, va_gc> **args,
+                              bool musthave)
+{
+  tree meth = lookup_promise_method (fn, member_id, loc, musthave);
+  if (meth == error_mark_node)
+    return error_mark_node;
+
+  /* If we don't find it, and it isn't needed, an empty return is OK.  */
+  if (!meth)
+    return NULL_TREE;
+
+  tree promise
+    = promise_obj ? promise_obj
+                 : get_coroutine_promise_proxy (current_function_decl);
+  tree expr;
+  if (BASELINK_P (meth))
+    expr = build_new_method_call (promise, meth, args, NULL_TREE,
+                                 LOOKUP_NORMAL, NULL, tf_warning_or_error);
+  else
+    {
+      expr = build_class_member_access_expr (promise, meth, NULL_TREE,
+                                            true, tf_warning_or_error);
+      vec<tree, va_gc> *real_args;
+      if (!args)
+       real_args = make_tree_vector ();
+      else
+       real_args = *args;
+      expr = build_op_call (expr, &real_args, tf_warning_or_error);
+    }
+  return expr;
+}
+
+/* Caching get for the expression p.return_void ().  */
+
+static tree
+get_coroutine_return_void_expr (tree decl, location_t loc, bool musthave)
+{
+  if (coroutine_info *info = get_coroutine_info (decl))
+    {
+      /* If we don't have it try to build it.  */
+      if (!info->return_void)
+       info->return_void
+         = coro_build_promise_expression (current_function_decl, NULL,
+                                          coro_return_void_identifier,
+                                          loc, NULL, musthave);
+      /* Don't return an error if it's an optional call.  */
+      if (!musthave && info->return_void == error_mark_node)
+       return NULL_TREE;
+      return info->return_void;
+    }
+  return musthave ? error_mark_node : NULL_TREE;
+}
+
 /* Lookup an Awaitable member, which should be await_ready, await_suspend
    or await_resume.  */
 
@@ -943,23 +1005,17 @@ finish_co_yield_expr (location_t kw, tree expr)
        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;
-
   /* [expr.yield] / 1
      Let e be the operand of the yield-expression and p be an lvalue naming
      the promise object of the enclosing coroutine, then the yield-expression
      is equivalent to the expression co_await p.yield_value(e).
      build p.yield_value(e):  */
   vec<tree, va_gc> *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, NULL, tf_warning_or_error);
+  tree yield_call
+    = coro_build_promise_expression (current_function_decl, NULL,
+                                    coro_yield_value_identifier, kw,
+                                    &args, /*musthave=*/true);
+  release_tree_vector (args);
 
   /* Now build co_await p.yield_value (e).
      Noting that for co_yield, there is no evaluation of any potential
@@ -1063,27 +1119,10 @@ finish_co_return_stmt (location_t kw, tree expr)
      there's a mis-match between the co_return <expr> and this.  */
   tree co_ret_call = error_mark_node;
   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 == 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);
-    }
+    co_ret_call
+      = get_coroutine_return_void_expr (current_function_decl, kw, true);
   else
     {
-      tree crv_meth
-       = lookup_promise_method (current_function_decl,
-                                coro_return_value_identifier, kw,
-                                /*musthave=*/true);
-      if (crv_meth == error_mark_node)
-       return error_mark_node;
-
       /* [class.copy.elision] / 3.
         An implicitly movable entity is a variable of automatic storage
         duration that is either a non-volatile object or an rvalue reference
@@ -1096,21 +1135,24 @@ finish_co_return_stmt (location_t kw, tree expr)
          && CLASS_TYPE_P (TREE_TYPE (expr))
          && !TYPE_VOLATILE (TREE_TYPE (expr)))
        {
-         vec<tree, va_gc> *args = make_tree_vector_single (move (expr));
          /* It's OK if this fails... */
-         co_ret_call = build_new_method_call
-           (get_coroutine_promise_proxy (current_function_decl), crv_meth,
-            &args, NULL_TREE, LOOKUP_NORMAL|LOOKUP_PREFER_RVALUE,
-            NULL, tf_none);
+         vec<tree, va_gc> *args = make_tree_vector_single (move (expr));
+         co_ret_call
+           = coro_build_promise_expression (current_function_decl, NULL,
+                                            coro_return_value_identifier, kw,
+                                            &args, /*musthave=*/false);
+         release_tree_vector (args);
        }
 
-      if (co_ret_call == error_mark_node)
+      if (!co_ret_call || co_ret_call == error_mark_node)
        {
-         vec<tree, va_gc> *args = make_tree_vector_single (expr);
          /* ... but this must succeed if we didn't get the move variant.  */
-         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);
+         vec<tree, va_gc> *args = make_tree_vector_single (expr);
+         co_ret_call
+           = coro_build_promise_expression (current_function_decl, NULL,
+                                            coro_return_value_identifier, kw,
+                                            &args, /*musthave=*/true);
+         release_tree_vector (args);
        }
     }
 
@@ -2585,23 +2627,18 @@ get_fn_local_identifier (tree orig, const char *append)
   return get_identifier (an);
 }
 
+/* Build an initial or final await initialized from the promise
+   initial_suspend or final_suspend expression.  */
+
 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;
+  tree setup_call
+    = coro_build_promise_expression (current_function_decl, NULL, suspend_alt,
+                                    loc, NULL, /*musthave=*/true);
 
   /* So build the co_await for this */
   /* For initial/final suspends the call is "a" per [expr.await] 3.2.  */
@@ -3918,22 +3955,17 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
     The unqualified-id get_return_object_on_allocation_failure is looked up
     in the scope of the promise type by class member access lookup.  */
 
-  tree grooaf_meth
-    = lookup_promise_method (orig, coro_gro_on_allocation_fail_identifier,
-                            fn_start, /*musthave=*/false);
-
-  tree grooaf = NULL_TREE;
-  tree dummy_promise = build_dummy_object (get_coroutine_promise_type (orig));
-
-  /* We don't require this, so lookup_promise_method can return NULL,
+  /* We don't require this, so coro_build_promise_expression can return NULL,
      but, if the lookup succeeds, then the function must be usable.  */
-  if (grooaf_meth && BASELINK_P (grooaf_meth))
-    grooaf = build_new_method_call (dummy_promise, grooaf_meth, NULL,
-                                   NULL_TREE, LOOKUP_NORMAL, NULL,
-                                   tf_warning_or_error);
-
-  /* ... but if that fails, returning an error, the later stages can't handle
-     the erroneous expression, so we reset the call as if it was absent.  */
+  tree dummy_promise = build_dummy_object (get_coroutine_promise_type (orig));
+  tree grooaf
+    = coro_build_promise_expression (orig, dummy_promise,
+                                    coro_gro_on_allocation_fail_identifier,
+                                    fn_start, NULL, /*musthave=*/false);
+
+  /* however, should that fail, returning an error, the later stages can't
+     handle the erroneous expression, so we reset the call as if it was
+     absent.  */
   if (grooaf == error_mark_node)
     grooaf = NULL_TREE;
 
@@ -3948,7 +3980,7 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
   if (TYPE_HAS_NEW_OPERATOR (promise_type))
     {
       tree fns = lookup_promise_method (orig, nwname, fn_start,
-                                   /*musthave=*/true);
+                                       /*musthave=*/true);
       /* [dcl.fct.def.coroutine] / 9 (part 2)
        If the lookup finds an allocation function in the scope of the promise
        type, overload resolution is performed on a function call created by
@@ -4274,12 +4306,10 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
   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);
+    = coro_build_promise_expression (orig, p,
+                                    coro_get_return_object_identifier,
+                                    fn_start, NULL, /*musthave=*/true);
   /* Without a return object we haven't got much clue what's going on.  */
   if (get_ro == error_mark_node)
     {
@@ -4491,14 +4521,10 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
      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, fn_start,
-                            /*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);
+  tree return_void
+    = coro_build_promise_expression (current_function_decl, ap,
+                                    coro_return_void_identifier,
+                                    fn_start, NULL, /*musthave=*/false);
 
   /* [stmt.return.coroutine] (2.2 : 3) if p.return_void() is a valid
      expression, flowing off the end of a coroutine is equivalent to
@@ -4516,13 +4542,10 @@ morph_fn_to_coro (tree orig, tree *resumer, tree *destroyer)
 
   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 (ap, ueh_meth, NULL, NULL_TREE, LOOKUP_NORMAL,
-                                NULL, tf_warning_or_error);
+       = coro_build_promise_expression (current_function_decl, ap,
+                                        coro_unhandled_exception_identifier,
+                                        fn_start, NULL, /*musthave=*/true);
 
       /* The try block is just the original function, there's no real
         need to call any function to do this.  */
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-00-return_void.C b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-00-return_void.C
new file mode 100644 (file)
index 0000000..2952d01
--- /dev/null
@@ -0,0 +1,63 @@
+//  { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+    std::suspend_never initial_suspend() const noexcept { return {}; }
+    std::suspend_never final_suspend() const noexcept { return {}; }
+    void unhandled_exception() const noexcept {}
+};
+
+int called_rv_op = 0;
+struct rv
+{
+    void operator ()(){
+        PRINT("call to operator ");
+        called_rv_op++;
+    }
+};
+
+struct pt_c : pt_b
+{
+    using handle_t = std::coroutine_handle<pt_c>;
+    auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+    rv return_void;
+};
+
+int called_lambda = 0;
+
+struct pt_d : pt_b
+{
+    using handle_t = std::coroutine_handle<pt_d>;
+    auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+    static constexpr auto return_void
+      = []{ PRINT("call to lambda "); called_lambda++; };
+};
+
+template <> struct std::coroutine_traits<pt_c::handle_t>
+    { using promise_type = pt_c; };
+
+static pt_c::handle_t foo ()
+{
+    co_return;
+}
+
+template <> struct std::coroutine_traits<pt_d::handle_t>
+    { using promise_type = pt_d; };
+
+static pt_d::handle_t bar ()
+{
+    co_return;
+}
+
+int main ()
+{
+    foo ();
+    bar ();
+    if (called_rv_op != 1 || called_lambda != 1)
+      {
+        PRINT ("Failed to call one of the return_void cases");
+        abort ();
+      }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-01-initial-suspend.C b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-01-initial-suspend.C
new file mode 100644 (file)
index 0000000..346c20d
--- /dev/null
@@ -0,0 +1,69 @@
+//  { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+    std::suspend_never final_suspend() const noexcept { return {}; }
+    constexpr void return_void () noexcept {};
+    void unhandled_exception() const noexcept {}
+};
+
+int called_is_op = 0;
+struct is
+{
+    std::suspend_never operator ()() noexcept {
+        PRINT("call to operator IS");
+        called_is_op++;
+        return {};
+    }
+};
+
+struct pt_c : pt_b
+{
+    using handle_t = std::coroutine_handle<pt_c>;
+    auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+    is initial_suspend;
+};
+
+int called_lambda = 0;
+struct pt_d : pt_b
+{
+    using handle_t = std::coroutine_handle<pt_d>;
+    auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+    static constexpr auto initial_suspend
+      = []() noexcept {
+          PRINT("call to lambda IS");
+          called_lambda++;
+          return std::suspend_never{};
+         };
+};
+
+template <>
+struct std::coroutine_traits<pt_c::handle_t>
+{ using promise_type = pt_c; };
+
+static pt_c::handle_t foo ()
+{
+    co_return;
+}
+
+template <>
+struct std::coroutine_traits<pt_d::handle_t>
+{ using promise_type = pt_d; };
+
+static pt_d::handle_t bar ()
+{
+    co_return;
+}
+
+int main ()
+{
+    foo ();
+    bar ();
+    if (called_is_op != 1 || called_lambda != 1)
+      {
+        PRINT ("Failed to call one of the initial_suspend cases");
+        abort ();
+      }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-02-final_suspend.C b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-02-final_suspend.C
new file mode 100644 (file)
index 0000000..8e7ba11
--- /dev/null
@@ -0,0 +1,69 @@
+//  { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+    std::suspend_never initial_suspend() const noexcept { return {}; }
+    constexpr void return_void () noexcept {};
+    void unhandled_exception() const noexcept {}
+};
+
+int called_fs_op = 0;
+struct fs
+{
+    auto operator ()() noexcept {
+        PRINT("call to operator FS");
+        called_fs_op++;
+        return std::suspend_never{};
+    }
+};
+
+struct pt_c : pt_b
+{
+    using handle_t = std::coroutine_handle<pt_c>;
+    auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+    fs final_suspend;
+};
+
+int called_lambda = 0;
+struct pt_d : pt_b
+{
+    using handle_t = std::coroutine_handle<pt_d>;
+    auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+    constexpr static auto final_suspend
+      = []() noexcept {
+          PRINT("call to lambda FS");
+          called_lambda++;
+          return std::suspend_never{};
+         };
+};
+
+template <>
+struct std::coroutine_traits<pt_c::handle_t>
+{ using promise_type = pt_c; };
+
+static pt_c::handle_t foo ()
+{
+    co_return;
+}
+
+template <>
+struct std::coroutine_traits<pt_d::handle_t>
+{ using promise_type = pt_d; };
+
+static pt_d::handle_t bar ()
+{
+    co_return;
+}
+
+int main ()
+{
+    foo ();
+    bar ();
+    if (called_fs_op != 1 || called_lambda != 1)
+      {
+        PRINT ("Failed to call one of the initial_suspend cases");
+        abort ();
+      }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-03-return-value.C b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-03-return-value.C
new file mode 100644 (file)
index 0000000..7d01509
--- /dev/null
@@ -0,0 +1,80 @@
+//  { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+    std::suspend_never initial_suspend() const noexcept { return {}; }
+    std::suspend_never final_suspend() const noexcept { return {}; }
+    void unhandled_exception() const noexcept {}
+};
+
+int called_rv_op = 0;
+int v;
+
+struct pt_c : pt_b
+{
+  struct rv
+    {
+      void operator ()(int x){
+        PRINTF("call to operator ret val with %d\n", x);
+        called_rv_op++;
+        v = x;
+//      int val () { return x; }
+    }
+  };
+  using handle_t = std::coroutine_handle<pt_c>;
+  auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+  rv return_value;
+};
+
+int called_lambda = 0;
+
+struct pt_d : pt_b
+{
+  using handle_t = std::coroutine_handle<pt_d>;
+  auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+  static constexpr auto return_value
+    = [] (int x) { PRINTF("call to lambda ret val %d\n", x); called_lambda++; v = x;};
+};
+
+template <> struct std::coroutine_traits<pt_c::handle_t>
+    { using promise_type = pt_c; };
+
+static pt_c::handle_t foo ()
+{
+    co_return 5;
+}
+
+template <> struct std::coroutine_traits<pt_d::handle_t>
+    { using promise_type = pt_d; };
+
+static pt_d::handle_t bar ()
+{
+    co_return 3;
+}
+
+int main ()
+{
+  /* These 'coroutines' run to completion imediately, like a regular fn.  */
+  foo ();
+  if (v != 5)
+     {
+      PRINT ("foo failed to set v");
+      abort ();
+    }
+
+  bar ();
+  if (v != 3)
+     {
+      PRINT ("bar failed to set v");
+      abort ();
+    }
+
+
+  if (called_rv_op != 1 || called_lambda != 1)
+    {
+      PRINT ("Failed to call one of the return_void cases");
+      abort ();
+    }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-04-yield-value.C b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-04-yield-value.C
new file mode 100644 (file)
index 0000000..d09e5ba
--- /dev/null
@@ -0,0 +1,84 @@
+//  { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+  std::suspend_never initial_suspend() const noexcept { return {}; }
+  std::suspend_never final_suspend() const noexcept { return {}; }
+  void unhandled_exception() const noexcept {}
+  constexpr static void return_void () noexcept {}
+};
+
+int called_yv_op = 0;
+int v;
+
+struct pt_c : pt_b
+{
+  struct yv
+    {
+      auto operator ()(int x){
+        PRINTF("call to operator yield val with %d\n", x);
+        called_yv_op++;
+        v = x;
+        return std::suspend_never{};
+    }
+  };
+  using handle_t = std::coroutine_handle<pt_c>;
+  auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+  yv yield_value;
+};
+
+int called_lambda = 0;
+struct pt_d : pt_b
+{
+  using handle_t = std::coroutine_handle<pt_d>;
+  auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+  static constexpr auto yield_value = [] (int x) -> std::suspend_never
+     {
+       PRINTF("call to lambda yield val %d\n", x);
+       called_lambda++;
+       v = x;
+       return {};
+    };
+};
+
+template <> struct std::coroutine_traits<pt_c::handle_t>
+    { using promise_type = pt_c; };
+
+static pt_c::handle_t foo ()
+{
+    co_yield 5;
+}
+
+template <> struct std::coroutine_traits<pt_d::handle_t>
+    { using promise_type = pt_d; };
+
+static pt_d::handle_t bar ()
+{
+    co_yield 3;
+}
+
+int main ()
+{
+  /* These 'coroutines' run to completion imediately, like a regular fn.  */
+  foo ();
+  if (v != 5)
+     {
+      PRINT ("foo failed to yield v");
+      abort ();
+    }
+
+  bar ();
+  if (v != 3)
+     {
+      PRINT ("bar failed to yield v");
+      abort ();
+    }
+
+  if (called_yv_op != 1 || called_lambda != 1)
+    {
+      PRINT ("Failed to call one of the return_void cases");
+      abort ();
+    }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-05-gro.C b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-05-gro.C
new file mode 100644 (file)
index 0000000..ba0a1e3
--- /dev/null
@@ -0,0 +1,64 @@
+//  { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+  std::suspend_always initial_suspend() const noexcept { return {}; }
+  std::suspend_never final_suspend() const noexcept { return {}; }
+  constexpr void return_void () noexcept {};
+  constexpr void unhandled_exception() const noexcept {}
+};
+
+int called_gro_op = 0;
+
+template<typename R, typename HandleRef, typename ...T>
+struct std::coroutine_traits<R, HandleRef, T...> {
+  struct pt_c;
+  using promise_type = pt_c;
+  struct pt_c : pt_b {
+    //using handle_t = std::coroutine_handle<pt_c>;
+    pt_c (HandleRef h, T ...args)
+    {  h = std::coroutine_handle<pt_c>::from_promise (*this);
+       PRINT ("Created Promise");
+       //g_promise = 1;
+    }
+    struct gro
+      {
+        auto operator ()() {
+        PRINT("call to operator ");
+        called_gro_op++;
+        }
+      };
+    gro get_return_object;
+  };
+};
+
+static void
+foo (std::coroutine_handle<>& h)
+{
+  co_return;
+}
+
+int main ()
+{
+  std::coroutine_handle<> f;
+  foo (f);
+  if (f.done())
+    {
+      PRINT ("unexptected finished f coro");
+      abort ();
+   }
+  f.resume();
+  if (!f.done())
+    {
+      PRINT ("expected f to be finished");
+      abort ();
+   }
+
+  if (called_gro_op != 1)
+    {
+      PRINT ("Failed to call gro op");
+      abort ();
+   }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-06-grooaf.C b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-06-grooaf.C
new file mode 100644 (file)
index 0000000..b6b6bd7
--- /dev/null
@@ -0,0 +1,49 @@
+//  { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+  constexpr std::suspend_never initial_suspend() noexcept { return {}; }
+  constexpr std::suspend_never final_suspend() noexcept { return {}; }
+  constexpr void return_void () noexcept {} 
+  constexpr void unhandled_exception() noexcept {}
+};
+
+int called_grooaf = 0;
+
+struct pt_c : pt_b
+{
+  using handle_t = std::coroutine_handle<pt_c>;
+  auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+
+  static constexpr auto get_return_object_on_allocation_failure
+      = []{ PRINT("call to lambda grooaf");
+           called_grooaf++; return std::coroutine_handle<pt_c>{};
+         };
+
+  /* Provide an operator new, that always fails.  */
+  void *operator new (std::size_t sz) noexcept {
+    PRINT ("promise_type: used failing op new");
+    return nullptr;
+  }
+};
+
+template <> struct std::coroutine_traits<pt_c::handle_t>
+    { using promise_type = pt_c; };
+
+static pt_c::handle_t
+foo ()
+{
+  co_return;
+}
+
+int main ()
+{
+  foo ();
+  if (called_grooaf != 1)
+    {
+      PRINT ("Failed to call grooaf");
+      abort ();
+    }
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/pr95519-07-unhandled-exception.C b/gcc/testsuite/g++.dg/coroutines/torture/pr95519-07-unhandled-exception.C
new file mode 100644 (file)
index 0000000..be2a928
--- /dev/null
@@ -0,0 +1,69 @@
+//  { dg-do run }
+
+#include "../coro.h"
+
+struct pt_b
+{
+  constexpr std::suspend_never initial_suspend() const noexcept { return {}; }
+  constexpr std::suspend_never final_suspend() const noexcept { return {}; }
+  constexpr void return_void () {};
+};
+
+int called_ueh_op = 0;
+struct ueh
+{
+  void operator ()() noexcept {
+    PRINT("call to operator UEH");
+    called_ueh_op++;
+  }
+};
+
+struct pt_c : pt_b
+{
+  using handle_t = std::coroutine_handle<pt_c>;
+  auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+  ueh unhandled_exception;
+};
+
+int lambda_ueh = 0;
+
+struct pt_d : pt_b
+{
+  using handle_t = std::coroutine_handle<pt_d>;
+  auto get_return_object() noexcept { return handle_t::from_promise(*this); }
+  static constexpr auto unhandled_exception
+    = [] () noexcept { PRINT("call to lambda UEH"); lambda_ueh++; };
+};
+
+template <>
+struct std::coroutine_traits<pt_c::handle_t>
+    { using promise_type = pt_c; };
+
+static pt_c::handle_t
+foo ()
+{
+  throw ("foo");
+  co_return;
+}
+
+template <> 
+struct std::coroutine_traits<pt_d::handle_t>
+    { using promise_type = pt_d; };
+
+static pt_d::handle_t
+bar ()
+{
+  throw ("bar");
+  co_return;
+}
+
+int main ()
+{
+  foo ();
+  bar ();
+  if (called_ueh_op != 1 || lambda_ueh != 1)
+    {
+      PRINT ("Failed to call one of the UEH cases");
+      abort ();
+    }
+}