Нативные векторы GNU C: как транслировать скаляр, например x86 _mm_set1_epi16

Как написать переносимый GNU C встроенный вектор, который не зависит от встроенного x86 set1?

typedef uint16_t v8su __attribute__((vector_size(16)));

v8su set1_u16_x86(uint16_t scalar) {
    return (v8su)_mm_set1_epi16(scalar);   // cast needed for gcc
}

Конечно, должен быть лучший способ, чем

v8su set1_u16(uint16_t s) {
    return (v8su){s,s,s,s,  s,s,s,s};
}

Я не хочу писать версию AVX2 для трансляции одного байта!

Даже ответ gcc-only или clang-only для этой части будет интересен, для случаев, когда вы хотите назначить переменную вместо использования только в качестве операнда для двоичного оператора (который хорошо работает с gcc, см. ниже).


Если я хочу использовать широковещательный скаляр как один операнд двоичного оператора, это работает с gcc (как описано в руководстве), но не с clang:

v8su vecdiv10(v8su v) { return v / 10; }   // doesn't compile with clang

С clang, если я нацелен только на x86 и просто использую собственный синтаксис векторных чтобы заставить компилятор генерировать модульные мультипликативные инверсные константы и инструкции для меня, я могу написать:

v8su vecdiv_set1(v8su v) {
    return v / (v8su)_mm_set1_epi16(10);   // gcc needs the cast
}

Но тогда мне нужно изменить внутреннее, если я увеличиваю вектор (до _mm256_set1_epi16) вместо того, чтобы преобразовать весь код в AVX2, изменив его на vector_size(32) в одном месте (для чисто-вертикального SIMD, который не нужно перетасовать). Он также побеждает часть целей нативных векторов, так как это не будет компилироваться для ARM или любой цели, отличной от x86.

Требуется уродливый бросок, потому что gcc, в отличие от clang, не считает v8us {aka __vector(8) short unsigned int} совместимым с __m128i {aka __vector(2) long long int}.

Кстати, все это компилируется в хороший asm с gcc и clang (видеть его на Godbolt). Это просто вопрос о том, как писать элегантно, с читаемым синтаксисом, который не повторяет скалярные N раз. например v / 10 достаточно компактен, что нет необходимости даже помещать его в свою собственную функцию.

Компиляция эффективно с ICC является бонусом, но не обязательным. Нативные векторы GNU C явно являются запоздалой мыслью для ICC, и даже простые вещи, подобные этому, не компилируются эффективно. set1_u16 компилируется в 8 скалярных хранилищ и векторную нагрузку вместо MOVD/VPBROADCASTW (при включенном -xHOST, поскольку он не распознает -march=haswell, но Godbolt работает на сервере с поддержкой AVX2). Чистое выполнение результатов _mm_ intrinsics в порядке, но деление вызывает функцию SVML!

Ответ 1

Общее решение широковещательной рассылки можно найти для GCC и Clang, используя два наблюдения

Вот решение для вектора из четырех поплавков.

#if defined (__clang__)
typedef float v4sf __attribute__((ext_vector_type(4)));
#else
typedef float v4sf __attribute__ ((vector_size (16)));
#endif

v4sf broadcast4f(float x) {
  return x - (v4sf){};
}

https://godbolt.org/g/PXr3Xb

Такое же общее решение может использоваться для разных векторов. Вот пример для вектора из восьми беззнаковых шорт.

#if defined (__clang__)
typedef unsigned short v8su __attribute__((ext_vector_type(8)));
#else
typedef unsigned short v8su __attribute__((vector_size(16)));
#endif

v8su broadcast8us(short x) {
  return x - (v8su){};
}

ICC (17) поддерживает подмножество векторных расширений GCC, но не поддерживает либо vector + scalar, либо vector*scalar, но тем не менее внутренние функции по-прежнему необходимы для трансляций. MSVC не поддерживает какой-либо вектор расширения.