diff mbox

C++ PATCH for P0135, C++17 guaranteed copy elision

Message ID CADzB+2nu0GQX_d74QGskn+GF9xOXDTXwo=7SsN4Xv_E7EvQ5bQ@mail.gmail.com
State New
Headers show

Commit Message

Jason Merrill Oct. 6, 2016, 9:26 p.m. UTC
Here's an update that handles more cases.
commit 726447a210cf92a85e8f9d014b85d958a0af62b5
Author: Jason Merrill <jason@redhat.com>
Date:   Wed Oct 5 19:31:09 2016 -0400

            C++17 copy elision improvements.
    
            * call.c (build_temp, convert_like_real): Don't re-copy
            TARGET_EXPR.  Handle packed fields.
            (build_x_va_arg): Wrap it in a TARGET_EXPR.
            (build_over_call): Add sanity check.
            * cvt.c (early_elide_copy): New.
            (ocp_convert): Use it.
            * except.c (build_throw): Use it.
            * init.c (get_nsdmi): Put back the TARGET_EXPR.
            (expand_default_init): Call early_elide_copy.
            * typeck.c (cp_build_modify_expr): Call early_elide_copy.
diff mbox

Patch

diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index dac1337..6feaf7e 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -6365,6 +6365,22 @@  build_temp (tree expr, tree type, int flags,
   int savew, savee;
   vec<tree, va_gc> *args;
 
+  *diagnostic_kind = DK_UNSPECIFIED;
+
+  if (TREE_CODE (expr) == CONSTRUCTOR)
+    expr = get_target_expr_sfinae (expr, complain);
+  if (early_elide_copy (type, expr))
+    return expr;
+
+  /* If the source is a packed field, calling the copy constructor will require
+     binding the field to the reference parameter to the copy constructor, and
+     we'll end up with an infinite loop.  If we can use a bitwise copy, then
+     do that now.  */
+  if ((lvalue_kind (expr) & clk_packed)
+      && CLASS_TYPE_P (TREE_TYPE (expr))
+      && !type_has_nontrivial_copy_init (TREE_TYPE (expr)))
+    return get_target_expr_sfinae (expr, complain);
+
   savew = warningcount + werrorcount, savee = errorcount;
   args = make_tree_vector_single (expr);
   expr = build_special_member_call (NULL_TREE, complete_ctor_identifier,
@@ -6374,8 +6390,6 @@  build_temp (tree expr, tree type, int flags,
     *diagnostic_kind = DK_WARNING;
   else if (errorcount > savee)
     *diagnostic_kind = DK_ERROR;
-  else
-    *diagnostic_kind = DK_UNSPECIFIED;
   return expr;
 }
 
@@ -6778,10 +6792,6 @@  convert_like_real (conversion *convs, tree expr, tree fn, int argnum,
 	flags |= LOOKUP_ONLYCONVERTING;
       if (convs->rvaluedness_matches_p)
 	flags |= LOOKUP_PREFER_RVALUE;
-      if (TREE_CODE (expr) == TARGET_EXPR
-	  && TARGET_EXPR_LIST_INIT_P (expr))
-	/* Copy-list-initialization doesn't actually involve a copy.  */
-	return expr;
       expr = build_temp (expr, totype, flags, &diag_kind, complain);
       if (diag_kind && complain)
 	{
@@ -7068,7 +7078,12 @@  build_x_va_arg (source_location loc, tree expr, tree type)
       return convert_from_reference (expr);
     }
 
-  return build_va_arg (loc, expr, type);
+  tree ret = build_va_arg (loc, expr, type);
+  if (CLASS_TYPE_P (type))
+    /* Wrap the VA_ARG_EXPR in a TARGET_EXPR now so other code doesn't need to
+       know how to handle it.  */
+    ret = get_target_expr (ret);
+  return ret;
 }
 
 /* TYPE has been given to va_arg.  Apply the default conversions which
@@ -7806,6 +7821,15 @@  build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
       else
 	arg = cp_build_indirect_ref (arg, RO_NULL, complain);
 
+      /* In C++17 we shouldn't be copying a TARGET_EXPR except into a base
+	 subobject.  */
+      if (CHECKING_P && cxx_dialect >= cxx1z)
+	gcc_assert (TREE_CODE (arg) != TARGET_EXPR
+		    // FIXME we shouldn't copy for direct-init either
+		    || !(flags & LOOKUP_ONLYCONVERTING)
+		    /* See unsafe_copy_elision_p.  */
+		    || DECL_BASE_CONSTRUCTOR_P (fn));
+
       /* [class.copy]: the copy constructor is implicitly defined even if
 	 the implementation elided its use.  */
       if (!trivial || DECL_DELETED_FN (fn))
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 49cbdf2..9282bbe 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -5692,6 +5692,7 @@  extern tree convert_to_reference		(tree, tree, int, int, tree,
 						 tsubst_flags_t);
 extern tree convert_from_reference		(tree);
 extern tree force_rvalue			(tree, tsubst_flags_t);
+extern bool early_elide_copy			(tree, tree);
 extern tree ocp_convert				(tree, tree, int, int,
 						 tsubst_flags_t);
 extern tree cp_convert				(tree, tree, tsubst_flags_t);
diff --git a/gcc/cp/cvt.c b/gcc/cp/cvt.c
index ecc8ef8..063457f 100644
--- a/gcc/cp/cvt.c
+++ b/gcc/cp/cvt.c
@@ -658,6 +658,27 @@  cp_convert_and_check (tree type, tree expr, tsubst_flags_t complain)
   return result;
 }
 
+/* Returns true if we should avoid even doing overload resolution for copying
+   EXPR to initialize a TYPE.  */
+
+bool
+early_elide_copy (tree type, tree expr)
+{
+  if (TREE_CODE (expr) != TARGET_EXPR)
+    return false;
+  /* List-initialization and direct-initialization don't involve a copy.  */
+  if (TARGET_EXPR_LIST_INIT_P (expr)
+      || TARGET_EXPR_DIRECT_INIT_P (expr))
+    return true;
+  /* In C++17, "If the initializer expression is a prvalue and the
+     cv-unqualified version of the source type is the same class as the class
+     of the destination, the initializer expression is used to initialize the
+     destination object."  */
+  return (cxx_dialect >= cxx1z
+	  && (same_type_ignoring_top_level_qualifiers_p
+	      (type, TREE_TYPE (expr))));
+}
+
 /* Conversion...
 
    FLAGS indicates how we should behave.  */
@@ -694,10 +715,8 @@  ocp_convert (tree type, tree expr, int convtype, int flags,
     return error_mark_node;
 
   if (MAYBE_CLASS_TYPE_P (type) && (convtype & CONV_FORCE_TEMP)
-      && !(cxx_dialect >= cxx1z
-	   && TREE_CODE (e) == TARGET_EXPR))
-    /* We need a new temporary; don't take this shortcut.  But in C++17, don't
-       force a temporary if we already have one.  */;
+      && !early_elide_copy (type, e))
+    /* We need a new temporary; don't take this shortcut.  */;
   else if (same_type_ignoring_top_level_qualifiers_p (type, TREE_TYPE (e)))
     {
       if (same_type_p (type, TREE_TYPE (e)))
diff --git a/gcc/cp/except.c b/gcc/cp/except.c
index 1c60b08..2f88082 100644
--- a/gcc/cp/except.c
+++ b/gcc/cp/except.c
@@ -683,7 +683,7 @@  build_throw (tree exp)
       object = cp_build_indirect_ref (object, RO_NULL, tf_warning_or_error);
 
       /* And initialize the exception object.  */
-      if (CLASS_TYPE_P (temp_type))
+      if (CLASS_TYPE_P (temp_type) && !early_elide_copy (temp_type, exp))
 	{
 	  int flags = LOOKUP_NORMAL | LOOKUP_ONLYCONVERTING;
 	  vec<tree, va_gc> *exp_vec;
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index d1c8274..63c3dab 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -584,9 +584,13 @@  get_nsdmi (tree member, bool in_ctor)
 	}
       /* Strip redundant TARGET_EXPR so we don't need to remap it, and
 	 so the aggregate init code below will see a CONSTRUCTOR.  */
-      if (init && SIMPLE_TARGET_EXPR_P (init))
+      bool simple_target = (init && SIMPLE_TARGET_EXPR_P (init));
+      if (simple_target)
 	init = TARGET_EXPR_INITIAL (init);
       init = break_out_target_exprs (init);
+      if (simple_target && TREE_CODE (init) != CONSTRUCTOR)
+	/* Now put it back so C++17 copy elision works.  */
+	init = get_target_expr (init);
     }
   current_class_ptr = save_ccp;
   current_class_ref = save_ccr;
@@ -1638,6 +1642,13 @@  expand_default_init (tree binfo, tree true_exp, tree exp, tree init, int flags,
 	init = reshape_init (type, init, complain);
     }
 
+  /* Also pull out a TARGET_EXPR that we want to avoid copying.  */
+  if (init && true_exp == exp
+      && TREE_CODE (init) == TREE_LIST
+      && list_length (init) == 1
+      && early_elide_copy (type, TREE_VALUE (init)))
+    init = TREE_VALUE (init);
+
   if (init && BRACE_ENCLOSED_INITIALIZER_P (init)
       && CP_AGGREGATE_TYPE_P (type))
     /* A brace-enclosed initializer for an aggregate.  In C++0x this can
@@ -1648,14 +1659,12 @@  expand_default_init (tree binfo, tree true_exp, tree exp, tree init, int flags,
      initializer, whether that happened just above or in
      cp_parser_late_parsing_nsdmi.
 
-     A TARGET_EXPR with TARGET_EXPR_DIRECT_INIT_P or TARGET_EXPR_LIST_INIT_P
-     set represents the whole initialization, so we shouldn't build up
-     another ctor call.  */
+     A TARGET_EXPR for which early_elide_copy is true represents the whole
+     initialization, so we shouldn't build up another ctor call.  */
+
   if (init
       && (TREE_CODE (init) == CONSTRUCTOR
-	  || (TREE_CODE (init) == TARGET_EXPR
-	      && (TARGET_EXPR_DIRECT_INIT_P (init)
-		  || TARGET_EXPR_LIST_INIT_P (init))))
+	  || early_elide_copy (type, init))
       && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (init), type))
     {
       /* Early initialization via a TARGET_EXPR only works for
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index c70cfc8..f1abb40 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -7616,6 +7616,8 @@  cp_build_modify_expr (location_t loc, tree lhs, enum tree_code modifycode,
 	}
       else if (! MAYBE_CLASS_TYPE_P (lhstype))
 	/* Do the default thing.  */;
+      else if (early_elide_copy (lhstype, rhs))
+	/* Do the default thing.  */;
       else
 	{
 	  vec<tree, va_gc> *rhs_vec = make_tree_vector_single (rhs);
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d9667e7..22af6e4 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -2325,7 +2325,11 @@  release of G++.
 The C++ standard allows an implementation to omit creating a temporary
 that is only used to initialize another object of the same type.
 Specifying this option disables that optimization, and forces G++ to
-call the copy constructor in all cases.
+call the copy constructor in all cases.  This option also causes G++
+to call trivial member functions which otherwise would be expanded inline.
+
+In C++17, the compiler is required to omit these temporaries, but this
+option still affects trivial member functions.
 
 @item -fno-enforce-eh-specs
 @opindex fno-enforce-eh-specs
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ctor14a.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ctor14a.C
index 644ae63..8d1ebcd 100644
--- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ctor14a.C
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ctor14a.C
@@ -8,7 +8,7 @@  struct A
 };
 
 constexpr A a;
-constexpr A b = A();		// { dg-error "" }
+constexpr A b = A();		// { dg-error "" "" { target c++14_down } }
 
 #define SA(X) static_assert ((X), #X)
 SA(a.p == &a);
diff --git a/gcc/testsuite/g++.dg/cpp0x/initlist9.C b/gcc/testsuite/g++.dg/cpp0x/initlist9.C
index bb6414c..ef806d0 100644
--- a/gcc/testsuite/g++.dg/cpp0x/initlist9.C
+++ b/gcc/testsuite/g++.dg/cpp0x/initlist9.C
@@ -8,7 +8,7 @@  struct b
   b() = default;
   ~b() = default;
   b& operator=(const b&) = delete;
-  b(const b&) = delete;		// { dg-message "declared" }
+  b(const b&) = delete;		// { dg-message "declared" "" { target c++14_down } }
 
   b(bool _t): t (_t) { }
 };
@@ -19,7 +19,7 @@  int main()
   b tst1 = { false };
 
   // copy initialization.
-  b tst2 = false;		// { dg-error "use" }
+  b tst2 = false;		// { dg-error "use" "" { target c++14_down } }
 
   // direct list initialization
   b tst3 { false };
diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept23.C b/gcc/testsuite/g++.dg/cpp0x/noexcept23.C
index 5a01df4..b630b23 100644
--- a/gcc/testsuite/g++.dg/cpp0x/noexcept23.C
+++ b/gcc/testsuite/g++.dg/cpp0x/noexcept23.C
@@ -10,5 +10,10 @@  void a(A) noexcept {}
 
 void f()
 {
-  static_assert(!noexcept(a(A{})), "");
+#if __cplusplus <= 201402L
+  const bool val = false;
+#else
+  const bool val = true;
+#endif
+  static_assert(noexcept(a(A{})) == val, "");
 }
diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept24.C b/gcc/testsuite/g++.dg/cpp0x/noexcept24.C
index c17ddfa..60ca443 100644
--- a/gcc/testsuite/g++.dg/cpp0x/noexcept24.C
+++ b/gcc/testsuite/g++.dg/cpp0x/noexcept24.C
@@ -13,7 +13,12 @@  void a(A<T>) noexcept {}
 template<typename T>
 void f()
 {
-  static_assert(!noexcept(a(A<T>{})), "");
+#if __cplusplus <= 201402L
+  const bool val = false;
+#else
+  const bool val = true;
+#endif
+  static_assert(val == noexcept(a(A<T>{})), "");
 }
 
 void g()
diff --git a/gcc/testsuite/g++.dg/cpp1z/elide1.C b/gcc/testsuite/g++.dg/cpp1z/elide1.C
index a0538bb..71476e2 100644
--- a/gcc/testsuite/g++.dg/cpp1z/elide1.C
+++ b/gcc/testsuite/g++.dg/cpp1z/elide1.C
@@ -14,3 +14,12 @@  A a2 = (42, A());
 A f();
 A a3 = f();
 A a4 = b ? A() : f();
+
+void g(A);
+A f() {
+  g(A());
+  if (b)
+    throw A();
+  else
+    return A();
+}
diff --git a/gcc/testsuite/g++.dg/init/copy3.C b/gcc/testsuite/g++.dg/init/copy3.C
index fa6a6ea..cfc7394 100644
--- a/gcc/testsuite/g++.dg/init/copy3.C
+++ b/gcc/testsuite/g++.dg/init/copy3.C
@@ -1,4 +1,4 @@ 
-// { dg-do run }
+// { dg-do run { target c++14_down } }
 // { dg-options "-fno-elide-constructors" }
 
 int copies;
diff --git a/gcc/testsuite/g++.dg/overload/arg3.C b/gcc/testsuite/g++.dg/overload/arg3.C
index 1684fcc..9507c02 100644
--- a/gcc/testsuite/g++.dg/overload/arg3.C
+++ b/gcc/testsuite/g++.dg/overload/arg3.C
@@ -11,12 +11,12 @@  struct A {};
 struct B : A
 {
   B(int);
-  B(B&);  // { dg-message "note" "" }
+  B(B&);  // { dg-message "note" "" { target c++14_down } }
 };
 
-void foo(B);			// { dg-message "initializing" }
+void foo(B);			// { dg-message "initializing" "" { target c++14_down } }
 
 void bar()
 {
-  foo(0); // { dg-error "" }
+  foo(0); // { dg-error "" "" { target c++14_down } }
 }
diff --git a/gcc/testsuite/g++.dg/template/copy1.C b/gcc/testsuite/g++.dg/template/copy1.C
index bf5a37c..a34221d 100644
--- a/gcc/testsuite/g++.dg/template/copy1.C
+++ b/gcc/testsuite/g++.dg/template/copy1.C
@@ -6,9 +6,9 @@ 
 
 struct A
 {
-  A(A&);			// { dg-message "A::A" }
-  template <class T> A(T); 	// { dg-message "A::A" }
+  A(A&);			// { dg-message "A::A" "" { target c++14_down } }
+  template <class T> A(T); 	// { dg-message "A::A" "" { target c++14_down } }
 };
 
-A a = 0; // { dg-error "" }
+A a = 0; // { dg-error "" "" { target c++14_down } }
 
diff --git a/gcc/testsuite/g++.old-deja/g++.eh/ctor1.C b/gcc/testsuite/g++.old-deja/g++.eh/ctor1.C
index 9b4adaf..21b27d7 100644
--- a/gcc/testsuite/g++.old-deja/g++.eh/ctor1.C
+++ b/gcc/testsuite/g++.old-deja/g++.eh/ctor1.C
@@ -2,7 +2,7 @@ 
 struct A
 {
   A();
-  A(A&);			// { dg-message "A::A|no known conversion" } referenced below
+  A(A&);			// { dg-message "A::A|no known conversion" "" { target c++14_down } } referenced below
 };
 
 int
@@ -10,8 +10,8 @@  main ()
 {
   try
     {
-      throw A();		// { dg-error "rvalue" "" } can't copy
-// { dg-error "thrown expression" "expr" { target *-*-* } 13 }
+      throw A();		// { dg-error "rvalue" "" { target c++14_down } } can't copy
+      // { dg-error "thrown expression" "expr" { target c++14_down } 13 }
     }
   catch (...) { }
 }
diff --git a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
index e557384..c855f8f 100644
--- a/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
+++ b/gcc/testsuite/g++.old-deja/g++.jason/temporary2.C
@@ -3,7 +3,7 @@  class X // Indentation has been done so to see the similarities.
 {
 public:
   X() {}
-         X(X& x) {x.i=7;} // { dg-message "note" } Both functions modify the
+         X(X& x) {x.i=7;} // { dg-message "note" "" { target c++14_down } } Both functions modify the
   void bar(X& x) {x.i=7;} // { dg-message "note" } reference parameter x.
   int i;
 };
@@ -12,6 +12,6 @@  X foo() { X x; return x; }
 
 int main() 
 {
-  X   x(foo()); // { dg-error "rvalue" } Compiler doesn't warn about temporary reference.
+  X   x(foo()); // { dg-error "rvalue" "" { target c++14_down } } Compiler doesn't warn about temporary reference.
   x.bar(foo()); // { dg-error "rvalue" } The same mistake is warned about in this case.
 }
diff --git a/gcc/testsuite/g++.old-deja/g++.mike/p2431.C b/gcc/testsuite/g++.old-deja/g++.mike/p2431.C
index 8a7ede6..d990a10 100644
--- a/gcc/testsuite/g++.old-deja/g++.mike/p2431.C
+++ b/gcc/testsuite/g++.old-deja/g++.mike/p2431.C
@@ -3,7 +3,7 @@ 
 class A
 {
 	public:
-      A(A &); // { dg-message "note" }
+      A(A &); // { dg-message "note" "" { target c++14_down } }
 };
 
 class B
@@ -18,6 +18,6 @@  class C
 	C()
 	{
 		B	b;
-		A a = b;// { dg-error "rvalue" }
+		A a = b;// { dg-error "rvalue" "" { target c++14_down } }
 	}
 };
diff --git a/gcc/testsuite/g++.old-deja/g++.pt/auto_ptr.C b/gcc/testsuite/g++.old-deja/g++.pt/auto_ptr.C
index 4a363a2..35e1cc2 100644
--- a/gcc/testsuite/g++.old-deja/g++.pt/auto_ptr.C
+++ b/gcc/testsuite/g++.old-deja/g++.pt/auto_ptr.C
@@ -9,7 +9,7 @@  template<typename X> struct auto_ptr {
    typedef X element_type;
 
    explicit auto_ptr(X* p =0) throw() : px(p) {}
-   auto_ptr(auto_ptr& r) throw() : px(r.release()) {} // { dg-message "note" } candidate
+   auto_ptr(auto_ptr& r) throw() : px(r.release()) {} // { dg-message "note" "" { target c++14_down } } candidate
    template<typename Y>
       auto_ptr(auto_ptr<Y>& r) throw() : px(r.release()) {}
 
@@ -44,12 +44,12 @@  struct Derived : Base { Derived() {} };
 
 auto_ptr<Derived> f() { auto_ptr<Derived> null(0); return null; }
 void g(auto_ptr<Derived>) { }
-void h(auto_ptr<Base>) { }	// { dg-message "initializing" }
+void h(auto_ptr<Base>) { }	// { dg-message "initializing" "" { target c++14_down } }
 
 int main() {
     auto_ptr<Base> x(f());
     auto_ptr<Derived> y(f());
     x = y;
     g(f());
-    h(f());			// { dg-error "rvalue" "" } no usable copy ctor
+    h(f());			// { dg-error "rvalue" "" { target c++14_down } } no usable copy ctor
 }