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