On 08/09/2026 11:34, Richard Biener wrote:
On Sat, Sep 5, 2026 at 7:39 PM feedable <[email protected]> wrote:

On 25/08/2026 13:48, Richard Biener wrote:
On Tue, Aug 25, 2026 at 1:26 AM feedable via Sourceware Forge
<[email protected]> wrote:
From: feedable <[email protected]>

Currently, auto_vec defaults its copy&move operations. This is bad,
since a user that tries to copy or move an auto_vec will unknowingly
set himself up for trouble, since the pointers from the base and members
will be copied verbatim, leading to double free situtaions and the like.
For auto_vec<T, N> with N != 0 we do not want these, instead they should
be deleted.  As seen in your implementation the complexity involved with
having to deal with the stack backing for small number of elements makes
this very inefficient.  If copy/move is needed people should use auto_vec<T, 0>
instead.
As stated in the discussion for v1, a move constructor is required for a
value to be returned. Values of type auto_vec<T, N> with N != 0 are
returned in the codebase already (yes, this is currently UB if the
constructor is not elided).
IMO those uses should be fixed.
I'm sorry but I still fail to understand the purpose of forbidding those
operations, given that an implementation of these operations is present
in this patch.

It is fairly clear to me that forbidding these operations will incur
costs on the codebase, such as denying storage of `auto_vec`s in
containers (which is being used in the following patch), and denying the
ability to return them (which is used now in the code elsewhere).

What is not clear to me is the benefits of such a decision.

Regarding the efficiency of my implementation of those ops, claims about
that can only be made in comparison, and the alternative here is to
unconditionally allocate on the heap, which is worse than storing the
data inline and eliding the move on return.

Until the net-benefits are clear to me, I cannot in good faith move
forward with this change.

Introduce proper constructor and operator overloads, that perform proper
transfer of memory controlled by auto_vec.

gcc/ChangeLog:

          * vec.cc (struct count_cdtors): Test utility type.
          (test_auto_vec_copy_move_construct): New test.
          (test_auto_vec_move_assign): Likewise.
          (test_auto_vec_copy_assign): Likewise.
          (vec_cc_tests): Likewise.
          * vec.h (vec<T, A, vl_embed>::quick_emplace): New function.
          (vec<T, va_heap, vl_ptr>::quick_emplace): Likewise.
          (vec<T, va_heap, vl_ptr>::safe_emplace): Likewise.
---
   gcc/vec.cc | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++
   gcc/vec.h  | 129 +++++++++++++++++++++++++++++++++++++++++++++++
   2 files changed, 272 insertions(+)

diff --git a/gcc/vec.cc b/gcc/vec.cc
index 8313adde2b22b..3d7fc387ba428 100644
--- a/gcc/vec.cc
+++ b/gcc/vec.cc
@@ -640,6 +640,146 @@ test_auto_delete_vec ()
     ASSERT_EQ (dtor_count, 2);
   }

+struct count_cdtors{
+  static int copy_cnt, move_cnt, ctor_cnt, dtor_cnt;
+  static void reset ()
+  {
+    copy_cnt = 0;
+    move_cnt = 0;
+    ctor_cnt = 0;
+    dtor_cnt = 0;
+  }
+  static void validate (int copy, int move, int ctor, int dtor)
+  {
+    ASSERT_EQ (copy_cnt, copy);
+    ASSERT_EQ (move_cnt, move);
+    ASSERT_EQ (ctor_cnt, ctor);
+    ASSERT_EQ (dtor_cnt, dtor);
+    reset ();
+  }
+  count_cdtors () { ++ctor_cnt; }
+  ~count_cdtors () { ++dtor_cnt; }
+  count_cdtors (count_cdtors &&) { ++move_cnt; }
+  count_cdtors (const count_cdtors &) { ++copy_cnt; }
+  count_cdtors &operator= (count_cdtors &&) { return ++move_cnt, *this; }
+  count_cdtors &operator= (const count_cdtors &) { return ++copy_cnt, *this; }
+};
+int count_cdtors::copy_cnt = 0,
+    count_cdtors::move_cnt = 0,
+    count_cdtors::ctor_cnt = 0,
+    count_cdtors::dtor_cnt = 0;
+
+static void
+test_auto_vec_copy_move_construct ()
+{
+  count_cdtors::reset ();
+  {
+    count_cdtors::validate (0, 0, 0, 0);
+    auto_vec<count_cdtors, 1> test (2);
+    count_cdtors::validate (0, 0, 0, 0);
+    test.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 2, 0);
+    auto test2 = test;
+    count_cdtors::validate (2, 0, 0, 0);
+    auto test3 = std::move (test);
+    count_cdtors::validate (0, 0, 0, 0);
+  }
+  count_cdtors::validate (0, 0, 0, 4);
+  {
+    auto_vec<count_cdtors, 2> test (2);
+    count_cdtors::validate (0, 0, 0, 0);
+    test.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 2, 0);
+    auto test2 = test;
+    count_cdtors::validate (2, 0, 0, 0);
+    auto test3 = std::move (test);
+    count_cdtors::validate (0, 2, 0, 0);
+  }
+  count_cdtors::validate (0, 0, 0, 6);
+}
+
+static void
+test_auto_vec_move_assign ()
+{
+  {
+    auto_vec<count_cdtors, 1> from (1), to (1);
+    from.quick_grow_cleared (1);
+    to.quick_grow_cleared (1);
+    count_cdtors::validate (0, 0, 2, 0);
+    to = std::move (from);
+    count_cdtors::validate (0, 1, 0, 0);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+  {
+    auto_vec<count_cdtors, 1> from (1), to (2);
+    from.quick_grow_cleared (1);
+    to.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 3, 0);
+    to = std::move (from);
+    count_cdtors::validate (0, 1, 0, 2);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+  {
+    auto_vec<count_cdtors, 1> from (2), to (1);
+    from.quick_grow_cleared (2);
+    to.quick_grow_cleared (1);
+    count_cdtors::validate (0, 0, 3, 0);
+    to = std::move (from);
+    count_cdtors::validate (0, 0, 0, 1);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+  {
+    auto_vec<count_cdtors, 1> from (2), to (2);
+    from.quick_grow_cleared (2);
+    to.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 4, 0);
+    to = std::move (from);
+    count_cdtors::validate (0, 0, 0, 2);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+}
+
+static void
+test_auto_vec_copy_assign ()
+{
+  {
+    auto_vec<count_cdtors, 1> from (1), to (1);
+    from.quick_grow_cleared (1);
+    to.quick_grow_cleared (1);
+    count_cdtors::validate (0, 0, 2, 0);
+    to = from;
+    count_cdtors::validate (1, 0, 0, 0);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+  {
+    auto_vec<count_cdtors, 1> from (1), to (2);
+    from.quick_grow_cleared (1);
+    to.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 3, 0);
+    to = from;
+    count_cdtors::validate (1, 0, 0, 1);
+  }
+  count_cdtors::validate (0, 0, 0, 2);
+  {
+    auto_vec<count_cdtors, 1> from (2), to (1);
+    from.quick_grow_cleared (2);
+    to.quick_grow_cleared (1);
+    count_cdtors::validate (0, 0, 3, 0);
+    to = from;
+    count_cdtors::validate (2, 0, 0, 1);
+  }
+  count_cdtors::validate (0, 0, 0, 4);
+  {
+    auto_vec<count_cdtors, 1> from (2), to (2);
+    from.quick_grow_cleared (2);
+    to.quick_grow_cleared (2);
+    count_cdtors::validate (0, 0, 4, 0);
+    to = from;
+    count_cdtors::validate (2, 0, 0, 0);
+  }
+  count_cdtors::validate (0, 0, 0, 4);
+}
+
   /* Verify accesses to vector elements are done indirectly.  */

   static void
@@ -828,6 +968,9 @@ vec_cc_tests ()
     test_qsort ();
     test_reverse ();
     test_auto_delete_vec ();
+  test_auto_vec_copy_move_construct ();
+  test_auto_vec_copy_assign ();
+  test_auto_vec_move_assign ();
     test_auto_alias ();
     test_string_slice_initializers ();
     test_string_slice_tokenize ();
diff --git a/gcc/vec.h b/gcc/vec.h
index 9d25678029832..fcab26d5c418d 100644
--- a/gcc/vec.h
+++ b/gcc/vec.h
@@ -620,6 +620,8 @@ public:
     void splice (const vec &);
     void splice (const vec *src);
     T *quick_push (const T &);
+  template<typename ...Ts>
+  T *quick_emplace (Ts &&...);
     using pop_ret_type
       = typename std::conditional <std::is_trivially_destructible <T>::value,
                                   T &, void>::type;
@@ -1053,6 +1055,16 @@ vec<T, A, vl_embed>::quick_push (const T &obj)
     return slot;
   }

+template<typename T, typename A>
+template<typename ...Ts>
+inline T *
+vec<T, A, vl_embed>::quick_emplace (Ts &&...obj)
+{
+  gcc_checking_assert (space (1));
+  T *slot = &address ()[m_vecpfx.m_num++];
+  ::new (static_cast<void*>(slot)) T ((Ts &&)obj...);
+  return slot;
+}

   /* Pop and return a reference to the last element off the end of the
      vector.  If T has non-trivial destructor, this method just pops
@@ -1623,7 +1635,11 @@ public:
     void splice (const vec &);
     void safe_splice (const vec & CXX_MEM_STAT_INFO);
     T *quick_push (const T &);
+  template<typename ...Ts>
+  T *quick_emplace (Ts &&...);
     T *safe_push (const T &CXX_MEM_STAT_INFO);
+  template<typename ...Ts>
+  T *safe_emplace (Ts &&...);
     using pop_ret_type
       = typename std::conditional <std::is_trivially_destructible <T>::value,
                                   T &, void>::type;
@@ -1691,6 +1707,91 @@ public:
       size_t off = (char *) &m_auto - (char *) this;
       this->m_vec = (vec<T, va_heap, vl_embed> *) ((char *) this + off);
     }
+  auto_vec (size_t s CXX_MEM_STAT_INFO, T fill): auto_vec (s)
+  {
+    for (size_t i = 0; i != s; ++i)
+      this->quick_push (fill);
+  }
+
+  auto_vec (auto_vec &&r)
+    noexcept (std::is_nothrow_move_constructible<T>::value)
+    : auto_vec ()
+  {
+    if (!r.using_auto_storage ())
+      {
+       this->m_vec = std::exchange (r.m_vec, nullptr);
+       return;
+      }
+    for (auto &v: r)
+      this->quick_emplace (std::move (v));
+  }
+
+  auto_vec &operator= (auto_vec &&r)
+    noexcept (std::is_nothrow_move_constructible<T>::value)
+  {
+    if (!r.using_auto_storage () && !this->using_auto_storage ())
+      {
+       std::swap (this->m_vec, r.m_vec);
+       r.release ();
+      }
+    else if (!r.using_auto_storage () && this->using_auto_storage ())
+      {
+       auto data = std::exchange (r.m_vec, nullptr);
+       std::exchange (this->m_vec, data)->truncate (0);
+      }
+    else if (r.using_auto_storage () && !this->using_auto_storage ())
+      {
+       /* To avoid losing temporaries, we have to fill the internal array
+          first, and only then destroy the residual elems.  */
+       m_auto.embedded_init (N, 0, 1);
+       vec<T, va_heap> replacement;
+       size_t off = (char *) &m_auto - (char *) this;
+       replacement.m_vec = (vec<T, va_heap, vl_embed> *) ((char *) this + off);
+       for (auto &v: r)
+         replacement.quick_emplace (std::move (v));
+       std::exchange ((vec<T, va_heap> &)*this, replacement).release ();
+      }
+    else if (r.using_auto_storage () && this->using_auto_storage ())
+      {
+       /* The most complicated case: move-assign all the elements that are live
+          in both, rthen move-construct or destruct the rest.  */
+       int a = this->length (), b = r.length ();
+       int i;
+       for (i = 0; i < a && i < b; ++i)
+         (*this)[i] = std::move (r[i]);
+       for (; i < b; ++i)
+         this->quick_emplace (std::move (r[i]));
+       this->truncate (b);
+      }
+    else gcc_unreachable ();
+    return *this;
+  }
+
+  auto_vec (const auto_vec &r): auto_vec (r.length ())
+  {
+    for (auto &v: r)
+      this->quick_push (v);
+  }
+
+  auto_vec &operator= (const auto_vec &r)
+  {
+    if (r.length () <= this->allocated ())
+      {
+       int a = this->length (), b = r.length ();
+       int i;
+       for (i = 0; i < a && i < b; ++i)
+         (*this)[i] = r[i];
+       for (; i < b; ++i)
+         this->quick_emplace (r[i]);
+       this->truncate (b);
+      }
+    else
+      {
+       auto_vec fresh = r;
+       *this = std::move (fresh);
+      }
+    return *this;
+  }

     ~auto_vec ()
     {
@@ -2082,6 +2183,19 @@ vec<T, va_heap, vl_ptr>::quick_push (const T &obj)
   }


+/* Construct a new element from constructor ARGS at the end of the vector.
+   There must be sufficient space in the vector.  Return a pointer to the slot
+   where the element was inserted.  */
+
+template<typename T>
+template<typename ...Ts>
+inline T *
+vec<T, va_heap, vl_ptr>::quick_emplace (Ts &&...args)
+{
+  return m_vec->quick_emplace ((Ts &&)args...);
+}
+
+
   /* Push a new element OBJ onto the end of this vector.  Reallocates
      the embedded vector, if needed.  Return a pointer to the slot where
      OBJ was inserted.  */
@@ -2095,6 +2209,21 @@ vec<T, va_heap, vl_ptr>::safe_push (const T &obj 
MEM_STAT_DECL)
   }


+/* Construct a new element from constructor ARGS at the end of the vector.
+   Reallocates the embedded vector, if needed.  Return a pointer to the slot
+   where the element was inserted.  */
+
+template<typename T>
+template<typename ...Ts>
+inline T *
+vec<T, va_heap, vl_ptr>::safe_emplace (Ts &&...args)
+{
+  reserve (1, false PASS_MEM_STAT);
+  return quick_emplace ((Ts &&)args...);
+}
+
+
+
   /* Pop and return a reference to the last element off the end of the
      vector.  If T has non-trivial destructor, this method just pops
      last element and returns void.  */
--
2.54.0

Reply via email to