On Tue, Sep 8, 2026 at 11:15 PM feedable <[email protected]> wrote:
>
>
> 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.

I'm not sure it is worse.

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

The argument is that once you expose an inefficient API you'll
get lazy uses and inefficiencies that could have been easily avoided.
But I might be overly pessimistic here, not knowing how often the
copy/move construction/assignment ops for auto_vec would occur.

There is also the semantic difference with vec<> where
assignment does a shallow copy of the m_vec pointer and _not_
copy the data as with your non-move assignment/ctor.  So can
we at least avoid that?  vec<> has vec<>::copy for this and
truncate() + splice() for the assignment.

We should possibly delete vec<>::operator=() and adjust code
to use const vec<>& argument passing (it's one more indirection
sadly).  Adding move assignment might be useful, but I'd like
to explicitly "see" it, like if we can force v1 = std::move (v2); or so?

Richard.

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