On Fri, 11 Oct 2024 at 07:48, Jonathan Wakely <jwak...@redhat.com> wrote: > > Tested x86_64-linux. > > -- >8 -- > > Currently we only optimize std::copy, std::copy_n etc. to memmove when > the source and destination types are the same. This means that we fail > to optimize copying between distinct 1-byte types, e.g. copying from a > buffer of std::byte to a buffer of unsigned char. > > This patch adds more partial specializations of the __memcpyable trait > so that we allow memcpy between integers of equal widths. This will > enable memmove for copying std::byte to unsigned char, and copying int > to unsigned, and long to long long (for I32LP64) or long to int (for > ILP32). > > Enabling the optimization needs to be based on the width of the integer > type, not just the size in bytes. This is because some targets define > non-standard integral types such as __int20 in msp430, which has padding > bits. It would not be safe to memcpy between e.g. __int20 and int32_t, > even though sizeof(__int20) == sizeof(int32_t). A new trait is > introduced to define the width, __memcpyable_integer, and then the > __memcpyable trait compares the widths. > > It's safe to copy between signed and unsigned integers of the same > width, because GCC only supports two's complement integers. > > We can also add the specialization __memcpyable_integer<byte> to enable > copying between narrow character types and std::byte.
Actually we can't enable memset for std::copy doing byte<->char, because the assignment that std::copy is supposed to use would be ill-formed. So even if the __memcpyable trait says it's OK, it won't compile. > > libstdc++-v3/ChangeLog: > > PR libstdc++/93059 > * include/bits/cpp_type_traits.h (__memcpyable): Add partial > specialization for pointers to distinct types. > (__memcpyable_integer): New trait to control which types can use > cross-type memcpy optimizations. > --- > libstdc++-v3/include/bits/cpp_type_traits.h | 88 ++++++++++++++++++++- > 1 file changed, 85 insertions(+), 3 deletions(-) > > diff --git a/libstdc++-v3/include/bits/cpp_type_traits.h > b/libstdc++-v3/include/bits/cpp_type_traits.h > index 19bf1edf647..8d386a36e62 100644 > --- a/libstdc++-v3/include/bits/cpp_type_traits.h > +++ b/libstdc++-v3/include/bits/cpp_type_traits.h > @@ -414,7 +414,7 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > typedef __true_type __type; > }; > > -#if __cplusplus >= 201703L > +#ifdef __glibcxx_byte // C++ >= 17 > enum class byte : unsigned char; > > template<> > @@ -434,8 +434,6 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > }; > #endif > > - template<typename> struct iterator_traits; > - > // A type that is safe for use with memcpy, memmove, memcmp etc. > template<typename _Tp> > struct __is_nonvolatile_trivially_copyable > @@ -459,16 +457,100 @@ __INT_N(__GLIBCXX_TYPE_INT_N_3) > enum { __value = 0 }; > }; > > + // Allow memcpy when source and destination are pointers to the same type. > template<typename _Tp> > struct __memcpyable<_Tp*, _Tp*> > : __is_nonvolatile_trivially_copyable<_Tp> > { }; > > + // Source pointer can be const. > template<typename _Tp> > struct __memcpyable<_Tp*, const _Tp*> > : __is_nonvolatile_trivially_copyable<_Tp> > { }; > > + template<typename _Tp> struct __memcpyable_integer; > + > + // For heterogeneous types, allow memcpy between equal-sized integers. > + template<typename _Tp, typename _Up> > + struct __memcpyable<_Tp*, _Up*> > + { > + enum { > + __value = __memcpyable_integer<_Tp>::__width != 0 > + && ((int)__memcpyable_integer<_Tp>::__width > + == (int)__memcpyable_integer<_Up>::__width) > + }; > + }; > + > + // Specialization for const U* because __is_integer<const U> is never true. > + template<typename _Tp, typename _Up> > + struct __memcpyable<_Tp*, const _Up*> > + : __memcpyable<_Tp*, _Up*> > + { }; > + > + template<typename _Tp> > + struct __memcpyable_integer > + { > + enum { > + __width = __is_integer<_Tp>::__value ? (sizeof(_Tp) * __CHAR_BIT__) : > 0 > + }; > + }; > + > + // Cannot memcpy volatile memory. > + template<typename _Tp> > + struct __memcpyable_integer<volatile _Tp> > + { enum { __width = 0 }; }; > + > +#ifdef __glibcxx_byte // C++ >= 17 > + // std::byte is not an integer, but is safe to memcpy to/from char. > + template<> > + struct __memcpyable_integer<byte> > + { enum { __width = __CHAR_BIT__ }; }; > +#endif > + > + // Specializations for __intNN types with padding bits. > +#if defined __GLIBCXX_TYPE_INT_N_0 && __GLIBCXX_BITSIZE_INT_N_0 % > __CHAR_BIT__ > + template<> > + struct __memcpyable_integer<__GLIBCXX_TYPE_INT_N_0> > + { enum { __width = __GLIBCXX_BITSIZE_INT_N_0 }; }; > + template<> > + struct __memcpyable_integer<unsigned __GLIBCXX_TYPE_INT_N_0> > + { enum { __width = __GLIBCXX_BITSIZE_INT_N_0 }; }; > +#endif > +#if defined __GLIBCXX_TYPE_INT_N_1 && __GLIBCXX_BITSIZE_INT_N_1 % > __CHAR_BIT__ > + template<> > + struct __memcpyable_integer<__GLIBCXX_TYPE_INT_N_1> > + { enum { __width = __GLIBCXX_BITSIZE_INT_N_1 }; }; > + template<> > + struct __memcpyable_integer<unsigned __GLIBCXX_TYPE_INT_N_1> > + { enum { __width = __GLIBCXX_BITSIZE_INT_N_1 }; }; > +#endif > +#if defined __GLIBCXX_TYPE_INT_N_2 && __GLIBCXX_BITSIZE_INT_N_2 % > __CHAR_BIT__ > + template<> > + struct __memcpyable_integer<__GLIBCXX_TYPE_INT_N_2> > + { enum { __width = __GLIBCXX_BITSIZE_INT_N_2 }; }; > + template<> > + struct __memcpyable_integer<unsigned __GLIBCXX_TYPE_INT_N_2> > + { enum { __width = __GLIBCXX_BITSIZE_INT_N_2 }; }; > +#endif > +#if defined __GLIBCXX_TYPE_INT_N_3 && __GLIBCXX_BITSIZE_INT_N_3 % > __CHAR_BIT__ > + template<> > + struct __memcpyable_integer<__GLIBCXX_TYPE_INT_N_3> > + { enum { __width = __GLIBCXX_BITSIZE_INT_N_3 }; }; > + template<> > + struct __memcpyable_integer<unsigned __GLIBCXX_TYPE_INT_N_3> > + { enum { __width = __GLIBCXX_BITSIZE_INT_N_3 }; }; > +#endif > + > +#if defined __STRICT_ANSI__ && defined __SIZEOF_INT128__ > + // In strict modes __is_integer<__int128> is false, > + // but we want to allow memcpy between signed/unsigned __int128. > + template<> > + struct __memcpyable_integer<__int128> { enum { __width = 128 }; }; > + template<> > + struct __memcpyable_integer<unsigned __int128> { enum { __width = 128 }; > }; > +#endif > + > // Whether two iterator types can be used with memcmp. > // This trait only says it's well-formed to use memcmp, not that it > // gives the right answer for a given algorithm. So for example, std::equal > -- > 2.46.2 >