P2993R2
Extend <bit> header function with overloads for std::simd

Published Proposal,

This version:
http://wg21.link/P2933R2
Authors:
(Intel)
(Intel)
Audience:
LEWG
Project:
ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21

Abstract

Proposal to extend std::simd with overloads from other C++ standard libraries

1. Motivation

[P1928R7] introduced data parallel types to C++. It mostly provided operators which worked on or with std::simd types, but it also included overloads of useful functions from other parts of C++ (e.g., sin, cos, abs). In this paper we propose some other functions from standard C++ headers which should receive overloads to work with std::simd types. The list isn’t exhaustive, but reflects those functions which are desirable to include.

2. Support for <bit>

The <bit> header is part of the numerics library and provides utilities for manipulating and querying the properties of integral values when treated as collections of bits. The table below summarises the contents of <bit>.

Name Purpose Proposed (Y/N)
endian A type which indicates the endianness of scalar types. N
bit_cast reinterpret the object representation of one type as that of another N
byteswap reverses the bytes in the given integer value Y
has_single_bit checks if a number is an integral power of two Y
bit_ceil finds the smallest integral power of two not less than the given value Y
bit_floor finds the largest integral power of two not greater than the given value Y
bit_width finds the smallest number of bits needed to represent the given value Y
rotl computes the result of bitwise left-rotation Y
rotr computes the result of bitwise right-rotation Y
countl_zero counts the number of consecutive 0 bits, starting from the most significant bit Y
countl_one counts the number of consecutive 1 bits, starting from the most significant bit Y
countr_zero counts the number of consecutive 0 bits, starting from the least significant bit Y
countr_one counts the number of consecutive 1 bits, starting from the least significant bit Y
popcount counts the number of 1 bits in an unsigned integer Y

Of these types and functions, only the first two shouldn’t be handled by std::simd:

All the other functions from <bit> should be handled in std::simd by element-wise application of the function to each element of the SIMD value. Any constraints and behaviours on the function will be applied at the SIMD value level. For instance, if byteswap participates in overload resolution only if the argument type satisfies std::integral concept then the overload of byteswap with std::simd parameter had the same constraint for std::simd<T, N>::value_type.

One small modification to the behaviour of <bit> for std::simd is where the return type differs to the input type. For example, the standard <bit> header defines some query functions as returning integer values:

template< class T >
constexpr int bit_width( T x ) noexcept;

template< class T >
constexpr int countl_one( T x ) noexcept;

If an int were to be returned from the std::simd overload of such functions then the size of the elements could change. For example, computing the bit width of a 8-bit integer could generate a std::simd of 64-bit integers as the output, which would lead to a dramatic change in storage size and performance. Instead, we propose that all the overloads for <bit> should return element types which are the same physical size as the element types they are querying. This would mean that calling bit_width on an unsigned 8-bit integer will return a std::simd containing signed 8-bit values.

3. Wording

Below, substitute the � character with a number the editor finds appropriate for the table, paragraph, section or sub-section.

3.1. Add new section in [version.syn]

The following section presents the wording to be applied against the C++ working draft.

In [version.syn] add:

 �    #define __cpp_lib_simd 20XXXXL // bump the version

3.2. Add new section [simd.bit]

basic_simd bit library [simd.bit]

template<typename T, typename Abi>
constexpr basic_simd<T, Abi> byteswap(const basic_simd<T, Abi>& n) noexcept;

template<typename T, typename Abi>
constexpr basic_simd<T, Abi>::mask_type has_single_bit(const basic_simd<T, Abi>& x) noexcept;

template<typename T, typename Abi>
constexpr basic_simd<T, Abi> bit_ceil(const basic_simd<T, Abi>& n) noexcept;

template<typename T, typename Abi>
constexpr basic_simd<T, Abi> bit_floor(const basic_simd<T, Abi>& n) noexcept;

template<typename T, typename Abi>
constexpr simd<std::make_signed_t<T>, basic_simd<T, Abi>>
bit_width(const basic_simd<T, Abi>& n) noexcept;

template<typename T, typename Abi>
[[nodiscard]] constexpr basic_simd<T, Abi> rotl(const basic_simd<T, Abi>& n) noexcept;

template<typename T, typename Abi>
[[nodiscard]] constexpr basic_simd<T, Abi> rotr(const basic_simd<T, Abi>& n) noexcept;

template<typename T, typename Abi>
constexpr simd<std::make_signed_t<T>, basic_simd<T, Abi>>
countl_zero(const basic_simd<T, Abi>& n) noexcept;

template<typename T, typename Abi>
constexpr simd<std::make_signed_t<T>, basic_simd<T, Abi>>
countl_one(const basic_simd<T, Abi>& n) noexcept;

template<typename T, typename Abi>
constexpr simd<std::make_signed_t<T>, basic_simd<T, Abi>>
countr_zero(const basic_simd<T, Abi>& n) noexcept;

template<typename T, typename Abi>
constexpr simd<std::make_signed_t<T>, basic_simd<T, Abi>>
countr_one(const basic_simd<T, Abi>& n) noexcept;

template<typename T, typename Abi>
constexpr simd<std::make_signed_t<T>, basic_simd<T, Abi>>
popcount(const basic_simd<T, Abi>& n) noexcept;

Constraints:

  • Any constraints from the equivalent scalar function from <bit> will be applied to the equivalent element-wise function in simd.

Returns:

  • A basic_simd with the same width as the input, where the ith element will be equal to the result of applying the same function to the ith element of the input.

  • The element size of the return value will always be the same physical size as that of each input element, even when the equivalent scalar function would return a larger type.

Remarks:

  • The order in which the functions are applied to each element is unspecified.

4. Revision History

R1 => R2

R0 => R1

References

Informative References

[P1928R7]
Matthias Kretz. std::simd - Merge data-parallel types from the Parallelism TS 2. 15 October 2023. URL: https://wg21.link/p1928r7