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
>

Reply via email to