[PATCH 1/2] libstdc++: Enable memcpy optimizations for distinct integral types [PR93059]
Jonathan Wakely
jwakely@redhat.com
Fri Oct 11 12:07:04 GMT 2024
On Fri, 11 Oct 2024 at 07:48, Jonathan Wakely <jwakely@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
>
More information about the Libstdc++
mailing list