Extracting the simd-vector length from a template parameter to use for a local type

Viewed 166

I'm struggling to find the right C++/clang utterance to solve the following problem. Notice first that the following doesn't overflow the unsigned short because of integer promotions before the arithmetic happens.

unsigned short testme = 16320;
testme = testme * 257 / 64;

The result is 65535. But when I use simd to try something similar on a vector of unsigned shorts, it doesn't work:

#import <simd/simd.h>

template <typename T>
  void muldiv( T* data, unsigned multiply, unsigned divide)
{
    *data = (*data * multiply) / divide;
}

...

simd::ushort4 testme = 16320;
muldiv( &testme, 257, 64);

This gives a vector of four 1023's. No integer promotion happened and the multiply wrapped. After looking about in the clang docs, the best I could come up with is this. Notice the caller has to provide a dummy parameter just to provide the working precision as a template type parameter.

#import <simd/simd.h>

template <typename T, typename W>
 void muldiv( T* data, unsigned multiply, unsigned divide, W workingtype)
{
    *data = __builtin_convertvector( (__builtin_convertvector(*data, W) * multiply) / divide, T);
}

...

simd::ushort4 testme = 16320;
muldiv( &testme, 257, 64, simd::uint4());

Now I get a vector of four 65535's. The reason T is a template parameter is sometimes I pass ushort4, ushort8, ushort16, etc. But I find it ugly to pass the working precision as a parameter as it is always unsigned int. I can't figure out a way to extract the simd-length from T so I can declare the type W locally. Something like this within the function would be nice:

typedef unsigned int W __attribute__((__vector_size__( ?? )));

But I can't figure out how to make that work. I tried something like this:

bool hopeful = __is_convertible_to( simd::ushort4, simd::uint4);

But hopeful always returns false.

Can anyone tell me the magic I need?

Note this is on Apple platforms which provides <simd/simd.h>.

1 Answers

So clang lets you pattern match on attributes and generate new attribute-modified types in templates.

So we can do this.

First pattern match on the simd width attribute:

template<class T>
struct get_simd_width;

template<class T, std::size_t x>
struct get_simd_width< __attribute__((__ext_vector_type__(x))) T >:
  std::integral_constant<std::size_t, x>
{};

Also, extract the underlying type of an attributed type:

template<class T>
struct get_simd_type;

template<class T, std::size_t x>
struct get_simd_type< __attribute__((__ext_vector_type__(x))) T >
{
  using type = T;
};

We then do some syntactic sugar to make them easier to use:

template<class T>
constexpr std::size_t simd_width = get_simd_width<T>{};
template<class T>
using simd_type = typename get_simd_type<T>::type;

This is to generate a new simd type with an attribute:

template<class T>
struct simd_helper;
template<class T, std::size_t N>
struct simd_helper<T[N]> {
  using type = __attribute__((__ext_vector_type__(N))) T;
};
template<class T>
using simd = typename simd_helper<T>::type;

Then simd<int[4]> makes a width-4 simd type.

Those should solve your problem. Live example.

template <class T>
void muldiv( T* data, unsigned multiply, unsigned divide)
{
   using W = simd<int[simd_width<T>]>;
   *data = __builtin_convertvector( (__builtin_convertvector(*data, W) * multiply) / divide, T);
}
Related