https://gcc.gnu.org/g:b69209936680dabbb7bbe08e71646a2c25ece0bf

commit r14-11740-gb69209936680dabbb7bbe08e71646a2c25ece0bf
Author: Iain Sandoe <iains....@gmail.com>
Date:   Thu Oct 31 08:40:08 2024 +0000

    c++/coroutines: Fix awaiter var creation [PR116506]
    
    Awaiters always need to have a coroutine state frame copy since
    they persist across potential supensions.  It simplifies the later
    analysis considerably to assign these early which we do when
    building co_await expressions.
    
    The cleanups in r15-3146-g47dbd69b1, unfortunately elided some of
    processing used to cater for cases where the var created from an
    xvalue, or is a pointer/reference type.
    
    Corrected thus.
    
            PR c++/116506
            PR c++/116880
    
    gcc/cp/ChangeLog:
    
            * coroutines.cc (build_co_await): Ensure that xvalues are
            materialised.  Handle references/pointer values in awaiter
            access expressions.
            (is_stable_lvalue): New.
            * decl.cc (cxx_maybe_build_cleanup): Handle null arg.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/coroutines/pr116506.C: New test.
            * g++.dg/coroutines/pr116880.C: New test.
    
    Signed-off-by: Iain Sandoe <i...@sandoe.co.uk>
    Co-authored-by: Jason Merrill <ja...@redhat.com>
    (cherry picked from commit 4c743798b1d4530b327dad7c606c610f3811fdbf)

Diff:
---
 gcc/cp/coroutines.cc                       | 106 +++++++++++++++--------------
 gcc/cp/decl.cc                             |   2 +-
 gcc/testsuite/g++.dg/coroutines/pr116506.C |  53 +++++++++++++++
 gcc/testsuite/g++.dg/coroutines/pr116880.C |  36 ++++++++++
 4 files changed, 144 insertions(+), 53 deletions(-)

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 09e2f3410627..8811d249c025 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -1066,6 +1066,28 @@ build_template_co_await_expr (location_t kw, tree type, 
tree expr, tree kind)
   return aw_expr;
 }
 
+/* Is EXPR an lvalue that will refer to the same object after a resume?
+
+   This is close to asking tree_invariant_p of its address, but that doesn't
+   distinguish temporaries from other variables.  */
+
+static bool
+is_stable_lvalue (tree expr)
+{
+  if (TREE_SIDE_EFFECTS (expr))
+    return false;
+
+  for (; handled_component_p (expr);
+       expr = TREE_OPERAND (expr, 0))
+    {
+      if (TREE_CODE (expr) == ARRAY_REF
+         && !TREE_CONSTANT (TREE_OPERAND (expr, 1)))
+       return false;
+    }
+
+  return (TREE_CODE (expr) == PARM_DECL
+         || (VAR_P (expr) && !is_local_temp (expr)));
+}
 
 /*  This performs [expr.await] bullet 3.3 and validates the interface obtained.
     It is also used to build the initial and final suspend points.
@@ -1128,7 +1150,7 @@ build_co_await (location_t loc, tree a, 
suspend_point_kind suspend_kind,
   if (o_type && !VOID_TYPE_P (o_type))
     o_type = complete_type_or_else (o_type, o);
 
-  if (!o_type)
+  if (!o_type || o_type == error_mark_node)
     return error_mark_node;
 
   if (TREE_CODE (o_type) != RECORD_TYPE)
@@ -1155,56 +1177,35 @@ build_co_await (location_t loc, tree a, 
suspend_point_kind suspend_kind,
   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.
-
-     If we need to materialize this as a temporary, then that will have to be
-     'promoted' to a coroutine frame var.  However, if the awaitable is a
-     user variable, parameter or comes from a scope outside this function,
-     then we must use it directly - or we will see unnecessary copies.
-
-     If o is a variable, find the underlying var.  */
-  tree e_proxy = STRIP_NOPS (o);
-  if (INDIRECT_REF_P (e_proxy))
-    e_proxy = TREE_OPERAND (e_proxy, 0);
-  while (TREE_CODE (e_proxy) == COMPONENT_REF)
+  /* [expr.await]/3.3 If o would be a prvalue, the temporary
+     materialization conversion ([conv.rval]) is applied.  */
+  if (!glvalue_p (o))
+    o = get_target_expr (o, tf_warning_or_error);
+
+  /* [expr.await]/3.4 e is an lvalue referring to the result of evaluating the
+     (possibly-converted) o.
+
+     So, either reuse an existing stable lvalue such as a variable or
+     COMPONENT_REF thereof, or create a new a coroutine state frame variable
+     for the awaiter, since it must persist across suspension.  */
+  tree e_var = NULL_TREE;
+  tree e_proxy = o;
+  if (is_stable_lvalue (o))
+    o = NULL_TREE; /* Use the existing entity.  */
+  else /* We need a non-temp var.  */
     {
-      e_proxy = TREE_OPERAND (e_proxy, 0);
-      if (INDIRECT_REF_P (e_proxy))
-       e_proxy = TREE_OPERAND (e_proxy, 0);
-      if (TREE_CODE (e_proxy) == CALL_EXPR)
+      tree p_type = TREE_TYPE (o);
+      tree o_a = o;
+      if (glvalue_p (o))
        {
-         /* We could have operator-> here too.  */
-         tree op = TREE_OPERAND (CALL_EXPR_FN (e_proxy), 0);
-         if (DECL_OVERLOADED_OPERATOR_P (op)
-             && DECL_OVERLOADED_OPERATOR_IS (op, COMPONENT_REF))
-           {
-             e_proxy = CALL_EXPR_ARG (e_proxy, 0);
-             STRIP_NOPS (e_proxy);
-             gcc_checking_assert (TREE_CODE (e_proxy) == ADDR_EXPR);
-             e_proxy = TREE_OPERAND (e_proxy, 0);
-           }
+         /* Build a reference variable for a non-stable lvalue o.  */
+         p_type = cp_build_reference_type (p_type, xvalue_p (o));
+         o_a = build_address (o);
+         o_a = cp_fold_convert (p_type, o_a);
        }
-      STRIP_NOPS (e_proxy);
-    }
-
-  /* Only build a temporary if we need it.  */
-  STRIP_NOPS (e_proxy);
-  if (TREE_CODE (e_proxy) == PARM_DECL
-      || (VAR_P (e_proxy) && !is_local_temp (e_proxy)))
-    {
-      e_proxy = o;
-      o = NULL_TREE; /* The var is already present.  */
-    }
-  else
-    {
-      tree p_type = o_type;
-      if (glvalue_p (o))
-       p_type = cp_build_reference_type (p_type, !lvalue_p (o));
-      e_proxy = get_awaitable_var (suspend_kind, p_type);
-      o = cp_build_modify_expr (loc, e_proxy, INIT_EXPR, o,
-                               tf_warning_or_error);
-      e_proxy = convert_from_reference (e_proxy);
+      e_var = get_awaitable_var (suspend_kind, p_type);
+      o = cp_build_init_expr (loc, e_var, o_a);
+      e_proxy = convert_from_reference (e_var);
     }
 
   /* I suppose we could check that this is contextually convertible to bool.  
*/
@@ -1270,7 +1271,7 @@ build_co_await (location_t loc, tree a, 
suspend_point_kind suspend_kind,
        return error_mark_node;
       if (coro_diagnose_throwing_fn (awrs_func))
        return error_mark_node;
-      if (tree dummy = cxx_maybe_build_cleanup (e_proxy, tf_none))
+      if (tree dummy = cxx_maybe_build_cleanup (e_var, tf_none))
        {
          if (CONVERT_EXPR_P (dummy))
            dummy = TREE_OPERAND (dummy, 0);
@@ -1281,7 +1282,8 @@ build_co_await (location_t loc, tree a, 
suspend_point_kind suspend_kind,
     }
 
   /* We now have three call expressions, in terms of the promise, handle and
-     'e' proxies.  Save them in the await expression for later expansion.  */
+     'e' proxy expression.  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().  */
@@ -1294,8 +1296,8 @@ build_co_await (location_t loc, tree a, 
suspend_point_kind suspend_kind,
     }
   TREE_VEC_ELT (awaiter_calls, 2) = awrs_call; /* await_resume().  */
 
-  if (REFERENCE_REF_P (e_proxy))
-    e_proxy = TREE_OPERAND (e_proxy, 0);
+  if (e_var)
+    e_proxy = e_var;
 
   tree awrs_type = TREE_TYPE (TREE_TYPE (awrs_func));
   tree suspend_kind_cst = build_int_cst (integer_type_node,
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index af9ef661a936..7a14cb2eb892 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -19082,7 +19082,7 @@ cxx_maybe_build_cleanup (tree decl, tsubst_flags_t 
complain)
   /* Assume no cleanup is required.  */
   cleanup = NULL_TREE;
 
-  if (error_operand_p (decl))
+  if (!decl || error_operand_p (decl))
     return cleanup;
 
   /* Handle "__attribute__((cleanup))".  We run the cleanup function
diff --git a/gcc/testsuite/g++.dg/coroutines/pr116506.C 
b/gcc/testsuite/g++.dg/coroutines/pr116506.C
new file mode 100644
index 000000000000..57a6e3605661
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr116506.C
@@ -0,0 +1,53 @@
+// { dg-do run }
+// { dg-additional-options "-fno-exceptions" }
+                                                       
+#include <coroutine>
+
+bool g_too_early = true;
+std::coroutine_handle<> g_handle;
+
+struct Awaiter {
+    Awaiter() {}
+    ~Awaiter() {
+        if (g_too_early) {
+            __builtin_abort ();
+        }
+    }
+
+    bool await_ready() { return false; }
+    void await_suspend(std::coroutine_handle<> handle) {
+        g_handle = handle;
+    }
+
+    void await_resume() {}
+};
+
+struct SuspendNever {
+    bool await_ready() noexcept { return true; }
+    void await_suspend(std::coroutine_handle<>) noexcept {}
+    void await_resume() noexcept {}
+};
+
+struct Coroutine {
+    struct promise_type {
+        Coroutine get_return_object() { return {}; }
+        SuspendNever initial_suspend() { return {}; }
+        SuspendNever final_suspend() noexcept { return {}; }
+        void return_void() {}
+        void unhandled_exception() {}
+
+        Awaiter&& await_transform(Awaiter&& u) {
+            return static_cast<Awaiter&&>(u);
+        }
+    };
+};
+
+Coroutine foo() {
+    co_await Awaiter{};
+}
+
+int main() {
+    foo();
+    g_too_early = false;
+    g_handle.destroy();
+}
diff --git a/gcc/testsuite/g++.dg/coroutines/pr116880.C 
b/gcc/testsuite/g++.dg/coroutines/pr116880.C
new file mode 100644
index 000000000000..f0db6a260445
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr116880.C
@@ -0,0 +1,36 @@
+#include <coroutine>
+
+struct promise_type;
+using handle_type = std::coroutine_handle<promise_type>;
+
+struct Co {
+    handle_type handle;
+    using promise_type = ::promise_type;
+
+    explicit Co(handle_type handle) : handle(handle) {}
+
+    bool await_ready() { return false; }
+    std::coroutine_handle<> await_suspend(handle_type handle);
+    void await_resume() {}
+};
+
+struct Done {};
+
+struct promise_type {
+    Co get_return_object();
+
+    std::suspend_always initial_suspend() { return {}; };
+    std::suspend_always final_suspend() noexcept { return {}; };
+    void return_value(Done) {}
+    void return_value(Co&&);
+    void unhandled_exception() { throw; };
+    Co&& await_transform(Co&& co) { return static_cast<Co&&>(co); }
+};
+
+Co tryToRun();
+
+Co init()
+{
+    co_await tryToRun();
+    co_return Done{};
+}

Reply via email to