Re: [PATCH] coroutines: Do not promote temporaries that will be elided.

2022-12-03 Thread Jason Merrill via Gcc-patches

On 12/2/22 15:25, Iain Sandoe wrote:

Thanks to Adrian for working on this and producing the revised test-cases.

This has been tested on x86_64-darwin21 with our testsuite, cppcoro and
a patched version of folly (that enables the experimental coroutines tests)
without regresssion (actually a handful of progressions on folly, plus the
fixes to our suite).


OK, glad the TARGET_EXPR_ELIDING_P work is being useful!


I am not sure how best to backport this; I think we want it at least on 12
and preferably earlier, but the TARGET_EXPR_ELIDING_P flag is not available
there, perhaps we can construct some local function in coroutines.cc that
emulates it? (or maybe the TARGET_EXPR_ELIDING_P and associated fixes is
suitable for backport?)


Backporting TARGET_EXPR_ELIDING_P doesn't seem feasible for 12.

If you wanted to try to handle it within coroutines, 
find_interesting_subtree could recognize various cases where the 
TARGET_EXPR is used as an initializer, i.e. RHS of an INIT_EXPR, 
TARGET_EXPR_INITIAL, element of CONSTRUCTOR, and maybe add them to 
temps_used like you already do for an INIT_EXPR at the top of a co_await.



thanks
Iain

-- *< --

We usually need to 'promote' (i.e. save to the coroutine frame) any temporary
variable that is in a target expression that must persist across an await
expression.  However, if the TE is just used as a direct initializer for
another object it will be elided - and we should not promote it since that
would lead to a DTOR call for something that is never constructed.

Since we now have a mechanism to tell if TEs will be elided, use that.

Although the PRs referenced initially appear to be different issues, they all
stem from this.

Co-Authored-By: Adrian Perl 
Signed-off-by: Iain Sandoe 

PR c++/100611
PR c++/101367
PR c++/101976
PR c++/99576

gcc/cp/ChangeLog:

* coroutines.cc (find_interesting_subtree): Do not promote temporaries
that are only used as direct initializers for some other object.

gcc/testsuite/ChangeLog:

* g++.dg/coroutines/pr100611.C: New test.
* g++.dg/coroutines/pr101367.C: New test.
* g++.dg/coroutines/pr101976.C: New test.
* g++.dg/coroutines/pr99576_1.C: New test.
* g++.dg/coroutines/pr99576_2.C: New test.
---
  gcc/cp/coroutines.cc|   1 +
  gcc/testsuite/g++.dg/coroutines/pr100611.C  |  94 +++
  gcc/testsuite/g++.dg/coroutines/pr101367.C  |  72 
  gcc/testsuite/g++.dg/coroutines/pr101976.C  |  78 
  gcc/testsuite/g++.dg/coroutines/pr99576_1.C | 124 
  gcc/testsuite/g++.dg/coroutines/pr99576_2.C |  72 
  6 files changed, 441 insertions(+)
  create mode 100644 gcc/testsuite/g++.dg/coroutines/pr100611.C
  create mode 100644 gcc/testsuite/g++.dg/coroutines/pr101367.C
  create mode 100644 gcc/testsuite/g++.dg/coroutines/pr101976.C
  create mode 100644 gcc/testsuite/g++.dg/coroutines/pr99576_1.C
  create mode 100644 gcc/testsuite/g++.dg/coroutines/pr99576_2.C

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 01a3e831ee5..3f23317a315 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -2685,6 +2685,7 @@ find_interesting_subtree (tree *expr_p, int *dosub, void 
*d)
}
  }
else if (tmp_target_expr_p (expr)
+  && !TARGET_EXPR_ELIDING_P (expr)
   && !p->temps_used->contains (expr))
  {
p->entry = expr_p;
diff --git a/gcc/testsuite/g++.dg/coroutines/pr100611.C 
b/gcc/testsuite/g++.dg/coroutines/pr100611.C
new file mode 100644
index 000..14edf4870a1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr100611.C
@@ -0,0 +1,94 @@
+// { dg-do run }
+/*
+  Test that instances created in capture clauses within co_await statements do 
not
+  get 'promoted'. This would lead to the members destructor getting called more
+  than once.
+
+  Correct output should look like:
+  Foo(23) 0xf042d8
+  Foo(const& 23) 0xf042ec
+  ~Foo(23) 0xf042ec
+  After co_await
+  ~Foo(23) 0xf042d8
+*/
+#include 
+#include 
+
+static unsigned int struct_Foo_destructor_counter = 0;
+static bool lambda_was_executed = false;
+
+class Task {
+public:
+  struct promise_type {
+Task get_return_object() {
+  return {std::coroutine_handle::from_promise(*this)};
+}
+
+std::suspend_never initial_suspend() { return {}; }
+std::suspend_always final_suspend() noexcept { return {}; }
+void unhandled_exception() {}
+void return_void() {}
+  };
+
+  ~Task() {
+if (handle_) {
+  handle_.destroy();
+}
+  }
+
+  bool await_ready() { return false; }
+  bool await_suspend(std::coroutine_handle<>) { return false; }
+  bool await_resume() { return false; }
+
+private:
+  Task(std::coroutine_handle handle) : handle_(handle) {}
+
+  std::coroutine_handle handle_;
+};
+
+class Foo {
+public:
+  Foo(int id) : id_(id) {
+std::cout << "Foo(" << id_ << ") " << (void*)this << std::endl;
+  }
+
+  Foo(Foo const& other) : 

[PATCH] coroutines: Do not promote temporaries that will be elided.

2022-12-02 Thread Iain Sandoe via Gcc-patches
Thanks to Adrian for working on this and producing the revised test-cases.

This has been tested on x86_64-darwin21 with our testsuite, cppcoro and
a patched version of folly (that enables the experimental coroutines tests)
without regresssion (actually a handful of progressions on folly, plus the
fixes to our suite).

I am not sure how best to backport this; I think we want it at least on 12
and preferably earlier, but the TARGET_EXPR_ELIDING_P flag is not available
there, perhaps we can construct some local function in coroutines.cc that
emulates it? (or maybe the TARGET_EXPR_ELIDING_P and associated fixes is
suitable for backport?)

thanks
Iain

-- *< --

We usually need to 'promote' (i.e. save to the coroutine frame) any temporary
variable that is in a target expression that must persist across an await
expression.  However, if the TE is just used as a direct initializer for
another object it will be elided - and we should not promote it since that
would lead to a DTOR call for something that is never constructed.

Since we now have a mechanism to tell if TEs will be elided, use that.

Although the PRs referenced initially appear to be different issues, they all
stem from this.

Co-Authored-By: Adrian Perl 
Signed-off-by: Iain Sandoe 

PR c++/100611
PR c++/101367
PR c++/101976
PR c++/99576

gcc/cp/ChangeLog:

* coroutines.cc (find_interesting_subtree): Do not promote temporaries
that are only used as direct initializers for some other object.

gcc/testsuite/ChangeLog:

* g++.dg/coroutines/pr100611.C: New test.
* g++.dg/coroutines/pr101367.C: New test.
* g++.dg/coroutines/pr101976.C: New test.
* g++.dg/coroutines/pr99576_1.C: New test.
* g++.dg/coroutines/pr99576_2.C: New test.
---
 gcc/cp/coroutines.cc|   1 +
 gcc/testsuite/g++.dg/coroutines/pr100611.C  |  94 +++
 gcc/testsuite/g++.dg/coroutines/pr101367.C  |  72 
 gcc/testsuite/g++.dg/coroutines/pr101976.C  |  78 
 gcc/testsuite/g++.dg/coroutines/pr99576_1.C | 124 
 gcc/testsuite/g++.dg/coroutines/pr99576_2.C |  72 
 6 files changed, 441 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr100611.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr101367.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr101976.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr99576_1.C
 create mode 100644 gcc/testsuite/g++.dg/coroutines/pr99576_2.C

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 01a3e831ee5..3f23317a315 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -2685,6 +2685,7 @@ find_interesting_subtree (tree *expr_p, int *dosub, void 
*d)
}
 }
   else if (tmp_target_expr_p (expr)
+  && !TARGET_EXPR_ELIDING_P (expr)
   && !p->temps_used->contains (expr))
 {
   p->entry = expr_p;
diff --git a/gcc/testsuite/g++.dg/coroutines/pr100611.C 
b/gcc/testsuite/g++.dg/coroutines/pr100611.C
new file mode 100644
index 000..14edf4870a1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/pr100611.C
@@ -0,0 +1,94 @@
+// { dg-do run }
+/*
+  Test that instances created in capture clauses within co_await statements do 
not
+  get 'promoted'. This would lead to the members destructor getting called more
+  than once.
+
+  Correct output should look like:
+  Foo(23) 0xf042d8
+  Foo(const& 23) 0xf042ec
+  ~Foo(23) 0xf042ec
+  After co_await
+  ~Foo(23) 0xf042d8
+*/
+#include 
+#include 
+
+static unsigned int struct_Foo_destructor_counter = 0;
+static bool lambda_was_executed = false;
+
+class Task {
+public:
+  struct promise_type {
+Task get_return_object() {
+  return {std::coroutine_handle::from_promise(*this)};
+}
+
+std::suspend_never initial_suspend() { return {}; }
+std::suspend_always final_suspend() noexcept { return {}; }
+void unhandled_exception() {}
+void return_void() {}
+  };
+
+  ~Task() {
+if (handle_) {
+  handle_.destroy();
+}
+  }
+
+  bool await_ready() { return false; }
+  bool await_suspend(std::coroutine_handle<>) { return false; }
+  bool await_resume() { return false; }
+
+private:
+  Task(std::coroutine_handle handle) : handle_(handle) {}
+
+  std::coroutine_handle handle_;
+};
+
+class Foo {
+public:
+  Foo(int id) : id_(id) {
+std::cout << "Foo(" << id_ << ") " << (void*)this << std::endl;
+  }
+
+  Foo(Foo const& other) : id_(other.id_) {
+std::cout << "Foo(const& " << id_ << ") " << (void*)this << std::endl;
+  }
+
+  Foo(Foo&& other) : id_(other.id_) {
+std::cout << "Foo(&& " << id_ << ") " << (void*)this << std::endl;
+  }
+
+  ~Foo() {
+std::cout << "~Foo(" << id_ << ") " << (void*)this << std::endl;
+struct_Foo_destructor_counter++;
+
+if (struct_Foo_destructor_counter > 2){
+  std::cout << "Foo was destroyed more than two times!\n";
+  __builtin_abort();
+}
+}
+
+private:
+  int id_;