https://gcc.gnu.org/g:0eaa676f6e15fab1a566fe410d23aaf83e05c36f

commit r16-9218-g0eaa676f6e15fab1a566fe410d23aaf83e05c36f
Author: Jason Merrill <[email protected]>
Date:   Thu Jul 2 17:54:22 2026 -0400

    c++: co_await and structured bindings [PR124584]
    
    Here the internal guard variables that control whether to clean up the
    structured binding variables were not living across the call to co_await
    because they weren't promoted into the coroutine frame.  The underlying
    problem is using a TARGET_EXPR, which lives only for the full-expression, to
    hold a value that needs to live as long as the variable itself.  So
    get_temp_regvar seems like a better fit.  We also need to manually pushdecl
    the guard variable so that it's visible to register_local_var_uses.
    
    It might be better to use the wrap_temporary_cleanups mechanism for the main
    variable, as we do for normal variables.  But let's go with the simple fix
    for now.
    
            PR c++/124584
    
    gcc/cp/ChangeLog:
    
            * decl.cc (cp_finish_decl): Use get_temp_regvar for decomp guards.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/coroutines/torture/tuple-decomp-pr124584.C: New test.
    
    (cherry picked from commit a085455306ec4aa15a11efacd994968bf951f7b6)

Diff:
---
 gcc/cp/decl.cc                                     | 15 +++---
 .../coroutines/torture/tuple-decomp-pr124584.C     | 54 ++++++++++++++++++++++
 2 files changed, 63 insertions(+), 6 deletions(-)

diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 04e685252dee..ee65b2fa3cdb 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -10114,10 +10114,14 @@ cp_finish_decl (tree decl, tree init, bool 
init_const_expr_p,
              tree guard = NULL_TREE;
              if (cleanups || cleanup)
                {
-                 guard = get_internal_target_expr (boolean_false_node);
-                 add_stmt (guard);
-                 guard = TARGET_EXPR_SLOT (guard);
+                 /* Since the CLEANUP_STMT will refer to the guard, we need it
+                     to be a variable with the same lifetime.  ??? It might be
+                     better to use wrap_temporary_cleanups.  */
+                 guard = get_temp_regvar (boolean_type_node, 
boolean_false_node);
+                 /* And make sure register_local_var_uses sees it.  */
+                 pushdecl (guard);
                }
+
              tree sl = push_stmt_list ();
              initialize_local_var (decl, init, true);
              if (guard)
@@ -10143,9 +10147,8 @@ cp_finish_decl (tree decl, tree init, bool 
init_const_expr_p,
                     popped that all, so push those extra cleanups around
                     the whole sequence with a guard variable.  */
                  gcc_assert (TREE_CODE (sl) == STATEMENT_LIST);
-                 guard = get_internal_target_expr (integer_zero_node);
-                 add_stmt (guard);
-                 guard = TARGET_EXPR_SLOT (guard);
+                 guard = get_temp_regvar (integer_type_node, 
integer_zero_node);
+                 pushdecl (guard);
                  for (unsigned i = 0; i < n_extra_cleanups; ++i)
                    {
                      tree_stmt_iterator tsi = tsi_last (sl);
diff --git a/gcc/testsuite/g++.dg/coroutines/torture/tuple-decomp-pr124584.C 
b/gcc/testsuite/g++.dg/coroutines/torture/tuple-decomp-pr124584.C
new file mode 100644
index 000000000000..8679d52bf09c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/torture/tuple-decomp-pr124584.C
@@ -0,0 +1,54 @@
+#include <coroutine>
+#include <tuple>
+
+static int g_dtor_count = 0;
+
+struct guard {
+  bool live_ = true;
+  ~guard() { if (live_) ++g_dtor_count; }
+  guard() = default;
+  guard(guard&& o) noexcept
+    : live_(o.live_) {o.live_ = false;}
+};
+
+static std::coroutine_handle<> g_pending;
+
+struct yield_aw {
+  bool await_ready() const noexcept { return false; }
+  void await_suspend(std::coroutine_handle<> h) noexcept
+  { g_pending = h; }
+  void await_resume() const noexcept {}
+};
+
+struct task {
+  struct promise_type {
+    task get_return_object() noexcept {
+      return {std::coroutine_handle<
+       promise_type>::from_promise(*this)};
+    }
+    std::suspend_never  initial_suspend() noexcept { return {}; }
+    std::suspend_always final_suspend()   noexcept { return {}; }
+    void return_void() noexcept {}
+    void unhandled_exception() { __builtin_abort(); }
+  };
+  std::coroutine_handle<promise_type> h_;
+  ~task() { if (h_) h_.destroy(); }
+};
+
+std::tuple<guard> make_guarded_tuple() { return {guard{}}; }
+
+task test_fn() {
+  auto [g] = make_guarded_tuple();   // not co_await
+  co_await yield_aw{};                // binding crosses suspend
+  (void)g;
+}
+
+int main() {
+  auto t = test_fn();
+  auto h = g_pending;
+  g_pending = nullptr;
+  h.resume();
+  __builtin_printf("dtor_count = %d  %s\n",
+                  g_dtor_count, g_dtor_count > 0 ? "OK" : "BUG");
+  return g_dtor_count > 0 ? 0 : 1;
+}

Reply via email to