https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105989

--- Comment #4 from Michal Jankovič <michal.jankovic59 at gmail dot com> ---
Comment on attachment 53273
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=53273
Experimental patch implementing the proposed transformation

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index edb3b706ddc..ed1ac4decaf 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -1997,6 +1997,7 @@ struct local_var_info
   bool is_static;
   bool has_value_expr_p;
   location_t def_loc;
+  vec<tree, va_gc> *field_access_path;
 };

 /* For figuring out what local variable usage we have.  */
@@ -2009,6 +2010,26 @@ struct local_vars_transform
   hash_map<tree, local_var_info> *local_var_uses;
 };

+/* Build a COMPONENT_REF chain for accessing a nested variable in the
coroutine
+   frame.  */
+static tree
+build_local_var_frame_access_expr (local_vars_transform *lvt,
+                                  local_var_info *local_var)
+{
+  tree access_expr = lvt->actor_frame;
+
+  for (tree path_elem_id : *local_var->field_access_path)
+    {
+      tree path_elem_member = lookup_member (
+       TREE_TYPE (access_expr), path_elem_id, 1, 0, tf_warning_or_error);
+      access_expr = build3_loc (
+       lvt->loc, COMPONENT_REF, TREE_TYPE (path_elem_member),
+       access_expr, path_elem_member, NULL_TREE);
+    }
+
+  return access_expr;
+}
+
 static tree
 transform_local_var_uses (tree *stmt, int *do_subtree, void *d)
 {
@@ -2040,12 +2061,7 @@ transform_local_var_uses (tree *stmt, int *do_subtree,
void *d)
          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);
+         tree fld_idx = build_local_var_frame_access_expr (lvd, &local_var);
          local_var.field_idx = fld_idx;
          SET_DECL_VALUE_EXPR (lvar, fld_idx);
          DECL_HAS_VALUE_EXPR_P (lvar) = true;
@@ -3873,14 +3889,24 @@ analyze_fn_parms (tree orig)
 /* Small helper for the repetitive task of adding a new field to the coro
    frame type.  */

+static void
+coro_make_frame_entry_id (tree *field_list, tree id, tree fld_type,
+                         location_t loc)
+{
+  tree decl = build_decl (loc, FIELD_DECL, id, fld_type);
+  DECL_CHAIN (decl) = *field_list;
+  *field_list = decl;
+}
+
+/* Same as coro_make_frame_entry_id, but creates an identifier from string. 
*/
+
 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;
+  coro_make_frame_entry_id (field_list, id, fld_type, loc);
+
   return id;
 }

@@ -3894,6 +3920,8 @@ struct local_vars_frame_data
   location_t loc;
   bool saw_capture;
   bool local_var_seen;
+  tree orig;
+  vec<tree, va_gc> *field_access_path;
 };

 /* A tree-walk callback that processes one bind expression noting local
@@ -3912,6 +3940,21 @@ register_local_var_uses (tree *stmt, int *do_subtree,
void *d)

   if (TREE_CODE (*stmt) == BIND_EXPR)
     {
+      tree scope_field_id = NULL_TREE;
+      if (lvd->nest_depth != 0)
+       {
+         /* Create identifier under which fields for this bind-expression will
+            be accessed.  */
+         char *scope_field_name
+           = xasprintf ("_Scope%u_%u", lvd->nest_depth, lvd->bind_indx);
+         scope_field_id = get_identifier (scope_field_name);
+         free (scope_field_name);
+
+         vec_safe_push (lvd->field_access_path, scope_field_id);
+       }
+
+      tree scope_variables = NULL_TREE;
+
       tree lvar;
       unsigned serial = 0;
       for (lvar = BIND_EXPR_VARS (*stmt); lvar != NULL;
@@ -3980,17 +4023,99 @@ register_local_var_uses (tree *stmt, int *do_subtree,
void *d)

          /* 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,
+
+         local_var.field_id = coro_make_frame_entry (&scope_variables, buf,
                                                      lvtype, lvd->loc);
          free (buf);
          /* We don't walk any of the local var sub-trees, they won't contain
             any bind exprs.  */
+
+         local_var.field_access_path = make_tree_vector_copy (
+           lvd->field_access_path);
+         vec_safe_push (local_var.field_access_path, local_var.field_id);
        }
+
+      unsigned bind_indx = lvd->bind_indx;
+      tree* parent_field_list = lvd->field_list;
+
+      /* Collect the scope structs of child bind-expressions when recursing. 
*/
+      tree child_scopes = NULL_TREE;
+      lvd->field_list = &child_scopes;
+
+      /* Create identifier under which fields for child bind-expressions will
be
+        accessed.  */
+      char *child_scopes_field_name
+       = xasprintf ("_Scope_list%u_%u", lvd->nest_depth, bind_indx);
+      tree child_scopes_field_id = get_identifier (child_scopes_field_name);
+      free (child_scopes_field_name);
+
+      /* Recurse to child bind expressions.  */
       lvd->bind_indx++;
       lvd->nest_depth++;
+      vec_safe_push (lvd->field_access_path, child_scopes_field_id);
       cp_walk_tree (&BIND_EXPR_BODY (*stmt), register_local_var_uses, d,
NULL);
       *do_subtree = 0; /* We've done this.  */
+      lvd->field_access_path->pop ();
       lvd->nest_depth--;
+
+      /* Restore the parent field list.  */
+      lvd->field_list = parent_field_list;
+
+      if (child_scopes != NULL_TREE)
+       {
+         /* Create a union to house the child scopes, so that they are
+            overlapped in the coroutine frame.  */
+         char *child_scopes_union_name_suffix
+           = xasprintf ("Frame_scope_list%u_%u", lvd->nest_depth, bind_indx);
+         tree child_scopes_union_name = get_fn_local_identifier (
+           lvd->orig, child_scopes_union_name_suffix);
+         free (child_scopes_union_name_suffix);
+         tree child_scopes_union
+           = xref_tag (union_type, child_scopes_union_name);
+         DECL_CONTEXT (TYPE_NAME (child_scopes_union)) = current_scope ();
+         child_scopes_union = begin_class_definition (child_scopes_union);
+         TYPE_FIELDS (child_scopes_union) = child_scopes;
+         TYPE_BINFO (child_scopes_union) = make_tree_binfo (0);
+         BINFO_OFFSET (TYPE_BINFO (child_scopes_union)) = size_zero_node;
+         BINFO_TYPE (TYPE_BINFO (child_scopes_union)) = child_scopes_union;
+         child_scopes_union = finish_struct (child_scopes_union, NULL_TREE);
+
+         /* Add it to the current scope fields.  */
+         coro_make_frame_entry_id (
+           &scope_variables, child_scopes_field_id,
+           child_scopes_union, lvd->loc);
+       }
+
+      if (lvd->nest_depth == 0)
+       {
+         /* The outermost scope contains special variables, embed them
directly
+            in the coroutine frame without nesting.  */
+         *lvd->field_list = scope_variables;
+       } else
+       {
+         /* Create a struct for housing the vars of this bind-expr
+            in the coroutine frame.  */
+         char *scope_struct_name_suffix
+           = xasprintf ("Frame_scope%u_%u", lvd->nest_depth, bind_indx);
+         tree scope_struct_name
+           = get_fn_local_identifier (lvd->orig, scope_struct_name_suffix);
+         free (scope_struct_name_suffix);
+         tree scope_struct = xref_tag (record_type, scope_struct_name);
+         DECL_CONTEXT (TYPE_NAME (scope_struct)) = current_scope ();
+         scope_struct = begin_class_definition (scope_struct);
+         TYPE_FIELDS (scope_struct) = scope_variables;
+         TYPE_BINFO (scope_struct) = make_tree_binfo (0);
+         BINFO_OFFSET (TYPE_BINFO (scope_struct)) = size_zero_node;
+         BINFO_TYPE (TYPE_BINFO (scope_struct)) = scope_struct;
+         scope_struct = finish_struct (scope_struct, NULL_TREE);
+
+         /* Add the scope struct to the parent field list.  */
+         coro_make_frame_entry_id (parent_field_list, scope_field_id,
+                                   scope_struct,
+                                   lvd->loc);
+
+         lvd->field_access_path->pop ();
+       }
     }
   return NULL_TREE;
 }
@@ -4487,7 +4612,8 @@ morph_fn_to_coro (tree orig, tree *resumer, tree
*destroyer)
      would expect to delete unused entries later.  */
   hash_map<tree, local_var_info> local_var_uses;
   local_vars_frame_data local_vars_data
-    = {&field_list, &local_var_uses, 0, 0, fn_start, false, false};
+    = {&field_list, &local_var_uses, 0, 0, fn_start, false, false, orig,
+       make_tree_vector ()};
   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
diff --git a/gcc/testsuite/g++.dg/coroutines/pr105989.C
b/gcc/testsuite/g++.dg/coroutines/pr105989.C
new file mode 100644
index 00000000000..c8b9be634aa
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr105989.C
@@ -0,0 +1,124 @@
+//  { dg-do run }
+
+#include <cstddef>
+
+#include "coro.h"
+
+struct promise;
+
+struct task
+{
+  coro::coroutine_handle<promise> handle;
+
+  ~task ()
+  {
+    if (handle)
+      {
+       handle.destroy ();
+      }
+  }
+};
+
+std::size_t frame_size = 0u;
+
+struct promise
+{
+  coro::coroutine_handle<> continuation = coro::noop_coroutine ();
+
+  auto get_return_object () noexcept
+  {
+    return task{coro::coroutine_handle<promise>::from_promise (*this)};
+  }
+
+  void unhandled_exception () noexcept {}
+
+  void return_void () {}
+
+  auto initial_suspend () noexcept { return coro::suspend_always{}; }
+
+  auto final_suspend () noexcept
+  {
+    struct awaiter_type : coro::suspend_always
+    {
+      auto await_suspend (coro::coroutine_handle <promise> handle) noexcept
+      {
+       return handle.promise ().continuation;
+      }
+    };
+
+    return awaiter_type{};
+  }
+
+  void * operator new (std::size_t size)
+  {
+    frame_size = size;
+    return new std::byte[size];
+  }
+
+  void operator delete (void *ptr, std::size_t size)
+  {
+    return delete[] static_cast<std::byte *>(ptr);
+  }
+};
+
+auto operator co_await (task &&t)
+{
+  struct awaiter_type : coro::suspend_always
+  {
+    coro::coroutine_handle<promise> handle;
+
+    auto await_suspend (coro::coroutine_handle<promise> continuation)
+    {
+      handle.promise ().continuation = continuation;
+      return handle;
+    }
+  };
+
+  return awaiter_type{{}, t.handle};
+}
+
+template<typename... Args>
+struct coro::coroutine_traits<task, Args...>
+{
+  using promise_type = promise;
+};
+
+auto coro_3 () -> task
+{
+  co_return;
+}
+
+std::byte *arr_ptr = nullptr;
+
+task coro_2 ()
+{
+  {
+    std::byte arr[256];
+    co_await coro_3 ();
+    arr_ptr = arr;
+  }
+  {
+    std::byte arr[256];
+    co_await coro_3 ();
+    arr_ptr = arr;
+  }
+}
+
+task coro_1 ()
+{
+  std::byte arr[256];
+  co_await coro_3 ();
+  arr_ptr = arr;
+}
+
+int main ()
+{
+  coro_1 ();
+  auto first_frame_size = frame_size;
+  coro_2 ();
+  auto second_frame_size = frame_size;
+
+  /* coro_1 frame should be the same size as coro_2 frame.  */
+
+  return first_frame_size == second_frame_size ? 0 : 1;
+}

Reply via email to