coroutines: Implicitly movable objects should use move CTORs for co_return.

Message ID A9DE2E05-DC13-4730-A3B8-4256534D4F25@sandoe.co.uk
State New
Headers show
Series
  • coroutines: Implicitly movable objects should use move CTORs for co_return.
Related show

Commit Message

Iain Sandoe May 13, 2020, 10:59 a.m.
.. and now to the right list…

I came across a build failure in a folly experimental test case where,
at first, it appeared that GCC was DTRT … however, further
investigation concluded that this was a case of differing interpretations
between implementations.

It’s kinda unhelpful that the discussion appears in class elision
but it motivates the selection of the correct overload even in cases
where there’s no elision.

The (conflicting info) issue is being taken to WG21 / Core.

tested on x86_64-darwin so far,
OK master after regstrap on Linux?
for 10.2 after some bake time on master?
thanks
Iain

----

This is a case where the standard contains conflicting information.
after discussion between implementators, the accepted intent is of
[class.copy.elision].  This amends the handling of co_return statements
to follow that.

gcc/cp/ChangeLog:

2020-05-13  Iain Sandoe  <iain@sandoe.co.uk>

	* coroutines.cc (finish_co_return_stmt): Implement rules
	from [class.copy.elision] /3.

gcc/testsuite/ChangeLog:

2020-05-13  Iain Sandoe  <iain@sandoe.co.uk>

	* g++.dg/coroutines/co-return-syntax-10-movable.C: New test.
---
 gcc/cp/coroutines.cc                          | 56 +++++++++++-----
 .../coroutines/co-return-syntax-10-movable.C  | 67 +++++++++++++++++++
 2 files changed, 107 insertions(+), 16 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/coroutines/co-return-syntax-10-movable.C

-- 
2.24.1

Comments

Nathan Sidwell May 13, 2020, 1:17 p.m. | #1
On 5/13/20 6:59 AM, Iain Sandoe wrote:
> .. and now to the right list…

> 

> I came across a build failure in a folly experimental test case where,

> at first, it appeared that GCC was DTRT … however, further

> investigation concluded that this was a case of differing interpretations

> between implementations.

> 

> It’s kinda unhelpful that the discussion appears in class elision

> but it motivates the selection of the correct overload even in cases

> where there’s no elision.

> 

> The (conflicting info) issue is being taken to WG21 / Core.


Yeah, let's have an xrefing note at the co_return point.  not an 
intercal come-from at the eliding point!

> @@ -1045,25 +1044,50 @@ finish_co_return_stmt (location_t kw, tree expr)

>         if (!crv_meth || crv_meth == error_mark_node)

>   	return error_mark_node;

>   

> -      vec<tree, va_gc> *args = make_tree_vector_single (expr);

> -      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);

> +      /* [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

> +	 to a non-volatile object type.  For such objects in the context of

> +	 the co_return, the overload resolution should be carried out first

> +	 treating the object as an rvalue, if that fails, then we fall back

> +	 to regular overload resolution.  */

> +      tree obj = STRIP_NOPS (expr);

> +      if (TREE_TYPE (obj)

> +	  && TYPE_REF_P (TREE_TYPE (obj))

> +	  && TYPE_REF_IS_RVALUE (TREE_TYPE (obj)))

> +	obj = TREE_OPERAND (obj, 0);

> +

> +      if (TREE_CODE (obj) == PARM_DECL

> +	  || (VAR_P (obj)

> +	      && decl_storage_duration (obj) == dk_auto

> +	      && !TYPE_VOLATILE (TREE_TYPE (obj))))


this is wrong and insufficient. As I explained on IRC -- 'expressions of 
reference type are not a thing'.  We always bash them into an 
indirect-ref of the referency thing.

Look at check_return_value and how it handles NRVO.  There's a 
treat_lvalue_as_rvalue_p call that I'll bet you can use[*], along with a 
move wrapper, which maybe what you want rather than the below rvalue (expr).

a return_expr's operand is an INIT_EXPR or MODIFY_EXPR whose LHS is 
DECL_RESULT(fn).  So that's almost what you want here.  I think the 
general form is
  if (treat_lval_as_rval ())
    { silently try callexpr with moved operand }

if no joy either way
    { noisily try callexpr with unmoved operand }

[*] That is insufficient, because elision also applies to objects you've 
placed into the coro frame.  by the std those are still automatic 
storage objects, but inside the compiler they are of course 
COMPONENT_REFs (or do you cons up VAR_DECLS of reference type that refer 
to them?).   Further, the PARM_DECL case can;t occur here -- the actor 
fn's only parm is the coro frame pointer.
  Either way you'll need to teach TLAR a new trick, or clone it locally 
(probably the better choice).

in place of TLAR's (parm_ok && ...) I think you want
  (code == COMPONENT_REF && OPERAND (0) == coro_frame)
and of course move it outside the (irrelavant) context check

does that help?

nathan
-- 
Nathan Sidwell
Iain Sandoe May 13, 2020, 1:26 p.m. | #2
Nathan Sidwell <nathan@acm.org> wrote:

> On 5/13/20 6:59 AM, Iain Sandoe wrote:


>> @@ -1045,25 +1044,50 @@ finish_co_return_stmt (location_t kw, tree expr)

>>        if (!crv_meth || crv_meth == error_mark_node)

>>  	return error_mark_node;

>>  -      vec<tree, va_gc> *args = make_tree_vector_single (expr);

>> -      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);

>> +      /* [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

>> +	 to a non-volatile object type.  For such objects in the context of

>> +	 the co_return, the overload resolution should be carried out first

>> +	 treating the object as an rvalue, if that fails, then we fall back

>> +	 to regular overload resolution.  */

>> +      tree obj = STRIP_NOPS (expr);

>> +      if (TREE_TYPE (obj)

>> +	  && TYPE_REF_P (TREE_TYPE (obj))

>> +	  && TYPE_REF_IS_RVALUE (TREE_TYPE (obj)))

>> +	obj = TREE_OPERAND (obj, 0);

>> +

>> +      if (TREE_CODE (obj) == PARM_DECL

>> +	  || (VAR_P (obj)

>> +	      && decl_storage_duration (obj) == dk_auto

>> +	      && !TYPE_VOLATILE (TREE_TYPE (obj))))

>

> this is wrong and insufficient. As I explained on IRC -- 'expressions of  

> reference type are not a thing'.  We always bash them into an  

> indirect-ref of the referency thing.

>

> Look at check_return_value and how it handles NRVO.  There's a  

> treat_lvalue_as_rvalue_p call that I'll bet you can use[*], along with a  

> move wrapper, which maybe what you want rather than the below rvalue  

> (expr).

>

> a return_expr's operand is an INIT_EXPR or MODIFY_EXPR whose LHS is  

> DECL_RESULT(fn).  So that's almost what you want here.  I think the  

> general form is

> if (treat_lval_as_rval ())

>   { silently try callexpr with moved operand }

>

> if no joy either way

>   { noisily try callexpr with unmoved operand }

>

> [*] That is insufficient, because elision also applies to objects you've  

> placed into the coro frame.  by the std those are still automatic storage  

> objects, but inside the compiler they are of course COMPONENT_REFs (or do  

> you cons up VAR_DECLS of reference type that refer to them?).   Further,  

> the PARM_DECL case can;t occur here -- the actor fn's only parm is the  

> coro frame pointer.


This is the equivalent of finish_return_stmt () in the parser, it knows  
nothing of the eventual morphing of local vars (or parms) into frame  
references.

So I only need to handle what can be returned by "expr =  
cp_parser_expression (parser);”
dependent expressions are dealt with above, with an early return with  
“type_unknown_node”.

So, it might be I can just reuse the code you point to?

Iain
Nathan Sidwell May 13, 2020, 3:10 p.m. | #3
On 5/13/20 9:26 AM, Iain Sandoe wrote:
> Nathan Sidwell <nathan@acm.org> wrote:

> 

>> On 5/13/20 6:59 AM, Iain Sandoe wrote:

> 

>>> @@ -1045,25 +1044,50 @@ finish_co_return_stmt (location_t kw, tree expr)

>>>        if (!crv_meth || crv_meth == error_mark_node)

>>>      return error_mark_node;

>>>  -      vec<tree, va_gc> *args = make_tree_vector_single (expr);

>>> -      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);

>>> +      /* [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

>>> +     to a non-volatile object type.  For such objects in the context of

>>> +     the co_return, the overload resolution should be carried out first

>>> +     treating the object as an rvalue, if that fails, then we fall back

>>> +     to regular overload resolution.  */

>>> +      tree obj = STRIP_NOPS (expr);

>>> +      if (TREE_TYPE (obj)

>>> +      && TYPE_REF_P (TREE_TYPE (obj))

>>> +      && TYPE_REF_IS_RVALUE (TREE_TYPE (obj)))

>>> +    obj = TREE_OPERAND (obj, 0);

>>> +

>>> +      if (TREE_CODE (obj) == PARM_DECL

>>> +      || (VAR_P (obj)

>>> +          && decl_storage_duration (obj) == dk_auto

>>> +          && !TYPE_VOLATILE (TREE_TYPE (obj))))

>>

>> this is wrong and insufficient. As I explained on IRC -- 'expressions 

>> of reference type are not a thing'.  We always bash them into an 

>> indirect-ref of the referency thing.

>>

>> Look at check_return_value and how it handles NRVO.  There's a 

>> treat_lvalue_as_rvalue_p call that I'll bet you can use[*], along with 

>> a move wrapper, which maybe what you want rather than the below rvalue 

>> (expr).

>>

>> a return_expr's operand is an INIT_EXPR or MODIFY_EXPR whose LHS is 

>> DECL_RESULT(fn).  So that's almost what you want here.  I think the 

>> general form is

>> if (treat_lval_as_rval ())

>>   { silently try callexpr with moved operand }

>>

>> if no joy either way

>>   { noisily try callexpr with unmoved operand }

>>

>> [*] That is insufficient, because elision also applies to objects 

>> you've placed into the coro frame.  by the std those are still 

>> automatic storage objects, but inside the compiler they are of course 

>> COMPONENT_REFs (or do you cons up VAR_DECLS of reference type that 

>> refer to them?).   Further, the PARM_DECL case can;t occur here -- the 

>> actor fn's only parm is the coro frame pointer.

> 

> This is the equivalent of finish_return_stmt () in the parser, it knows 

> nothing of the eventual morphing of local vars (or parms) into frame 

> references.

> 

> So I only need to handle what can be returned by "expr = 

> cp_parser_expression (parser);”

> dependent expressions are dealt with above, with an early return with 

> “type_unknown_node”.

> 

> So, it might be I can just reuse the code you point to?


Yeah I think so.  I realized it was pre-xform so the component-ref stuff 
was irrelevant when out shopping.  In other news, flour's back in stock :)

nathan

-- 
Nathan Sidwell

Patch

diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
index 730e6fef82a..423c37e1c9c 100644
--- a/gcc/cp/coroutines.cc
+++ b/gcc/cp/coroutines.cc
@@ -999,11 +999,10 @@  finish_co_return_stmt (location_t kw, tree expr)
 	 expression as it is.  */
       if (dependent_type_p (functype) || type_dependent_expression_p (expr))
 	{
-	  expr
-	    = build2_loc (kw, CO_RETURN_EXPR, void_type_node, expr, NULL_TREE);
+	  expr = build2_loc (kw, CO_RETURN_EXPR, unknown_type_node,
+			     expr, NULL_TREE);
 	  expr = maybe_cleanup_point_expr_void (expr);
-	  expr = add_stmt (expr);
-	  return expr;
+	  return add_stmt (expr);
 	}
     }
 
@@ -1022,7 +1021,7 @@  finish_co_return_stmt (location_t kw, tree expr)
 
   /* If the promise object doesn't have the correct return call then
      there's a mis-match between the co_return <expr> and this.  */
-  tree co_ret_call = NULL_TREE;
+  tree co_ret_call = error_mark_node;
   if (expr == NULL_TREE || VOID_TYPE_P (TREE_TYPE (expr)))
     {
       tree crv_meth
@@ -1045,25 +1044,50 @@  finish_co_return_stmt (location_t kw, tree expr)
       if (!crv_meth || crv_meth == error_mark_node)
 	return error_mark_node;
 
-      vec<tree, va_gc> *args = make_tree_vector_single (expr);
-      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);
+      /* [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
+	 to a non-volatile object type.  For such objects in the context of
+	 the co_return, the overload resolution should be carried out first
+	 treating the object as an rvalue, if that fails, then we fall back
+	 to regular overload resolution.  */
+      tree obj = STRIP_NOPS (expr);
+      if (TREE_TYPE (obj)
+	  && TYPE_REF_P (TREE_TYPE (obj))
+	  && TYPE_REF_IS_RVALUE (TREE_TYPE (obj)))
+	obj = TREE_OPERAND (obj, 0);
+
+      if (TREE_CODE (obj) == PARM_DECL
+	  || (VAR_P (obj)
+	      && decl_storage_duration (obj) == dk_auto
+	      && !TYPE_VOLATILE (TREE_TYPE (obj))))
+	{
+	  vec<tree, va_gc> *args = make_tree_vector_single (rvalue (expr));
+	  co_ret_call = build_new_method_call
+	    (get_coroutine_promise_proxy (current_function_decl), crv_meth,
+	     &args, NULL_TREE, LOOKUP_NORMAL, NULL, tf_none);
+	}
+
+      if (co_ret_call == error_mark_node )
+	{
+	  vec<tree, va_gc> *args = make_tree_vector_single (expr);
+	  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);
+	  release_tree_vector (args);
+	}
     }
 
+  expr = build2_loc (kw, CO_RETURN_EXPR, void_type_node, expr, co_ret_call);
+  expr = maybe_cleanup_point_expr_void (expr);
+
   /* Makes no sense for a co-routine really. */
   if (TREE_THIS_VOLATILE (current_function_decl))
     warning_at (kw, 0,
 		"function declared %<noreturn%> has a"
 		" %<co_return%> statement");
 
-  if (!co_ret_call || co_ret_call == error_mark_node)
-    return error_mark_node;
-
-  expr = build2_loc (kw, CO_RETURN_EXPR, void_type_node, expr, co_ret_call);
-  expr = maybe_cleanup_point_expr_void (expr);
-  expr = add_stmt (expr);
-  return expr;
+  return add_stmt (expr);
 }
 
 /* We need to validate the arguments to __builtin_coro_promise, since the
diff --git a/gcc/testsuite/g++.dg/coroutines/co-return-syntax-10-movable.C b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-10-movable.C
new file mode 100644
index 00000000000..8c18e13bce2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/coroutines/co-return-syntax-10-movable.C
@@ -0,0 +1,67 @@ 
+// Check that we obey the extra rules for implicitly movable co_return
+// objects [class.copy.elision]/3.
+
+#include "coro.h"
+
+#include  <utility>
+
+template <typename T>
+struct coro1 {
+  struct promise_type;
+  using handle_type = coro::coroutine_handle<coro1::promise_type>;
+  handle_type handle;
+  coro1 () : handle(0) {}
+  coro1 (handle_type _handle)
+    : handle(_handle) { }
+  coro1 (const coro1 &) = delete; // no copying
+  coro1 (coro1 &&s) : handle(s.handle) { s.handle = nullptr;  }
+  coro1 &operator = (coro1 &&s) {
+    handle = s.handle;
+    s.handle = nullptr;
+    return *this;
+  }
+  ~coro1() {
+    if ( handle )
+      handle.destroy();
+  }
+
+  struct promise_type {
+  T value;
+  promise_type() {}
+  ~promise_type() {}
+
+  auto get_return_object () { return handle_type::from_promise (*this);}
+  std::suspend_always initial_suspend () const { return {}; }
+  std::suspend_always final_suspend () const {  return {}; }
+
+  void return_value(T&& v) noexcept { value = std::move(v); }
+  
+  T get_value (void) { return value; }
+  void unhandled_exception() { }
+  };
+};
+
+struct MoveOnlyType 
+{
+  int value_;
+
+  explicit MoveOnlyType() noexcept : value_(0) {}
+  explicit MoveOnlyType(int value) noexcept : value_(value) {}
+
+  MoveOnlyType(MoveOnlyType&& other) noexcept
+      : value_(std::exchange(other.value_, -1)) {}
+
+  MoveOnlyType& operator=(MoveOnlyType&& other) noexcept {
+    value_ = std::exchange(other.value_, -1);
+    return *this;
+  }
+
+  ~MoveOnlyType() { value_ = -2; }
+};
+
+coro1<MoveOnlyType> 
+my_coro ()
+{
+  MoveOnlyType x{10};
+  co_return x;
+}