Spec-Zone.ru › GCC 11

Далее: Поддержка offsetof, Предыдущее: Получение адреса возврата или фрейма функции, Наверх: Расширения для языков семейства C [Оглавление][Индекс]

6.52 Использование векторных инструкций через встроенные функции ¶

На некоторых целевых платформах набор инструкций содержит векторные SIMD-инструкции, которые работают с несколькими значениями, содержащимися в одном большом регистре одновременно. Например, на x86 можно использовать расширения MMX, 3DNow! и SSE.

Первый шаг в использовании этих расширений — предоставление необходимых типов данных. Это следует делать с использованием соответствующего typedef:

typedef int v4si __attribute__ ((vector_size (16)));

Тип int определяет базовый тип, а атрибут указывает размер вектора для переменной, измеряемый в байтах. Например, объявление выше приводит к установке режима для типа v4si в 16 байт и разделению на блоки размером int. Для 32-битного int это означает вектор из 4 блоков по 4 байта, и соответствующий режим foo — V4SI.

Атрибут vector_size применим только к целочисленным и плавающим скалярам, хотя массивы, указатели и значения возврата функций разрешены в сочетании с этим конструктом. В настоящее время разрешены только размеры, являющиеся положительными степенями двойки, кратные размеру базового типа.

Все основные целочисленные типы могут использоваться в качестве базовых типов, как со знаком, так и без знака: char, short, int, long, long long. Кроме того, float и double можно использовать для построения векторных типов с плавающей точкой.

Указание комбинации, которая не является допустимой для текущей архитектуры, заставляет GCC синтезировать инструкции с более узким режимом. Например, если вы укажете переменную типа V4SI и ваша архитектура не поддерживает этот конкретный тип SIMD, GCC генерирует код, использующий 4 SIs.

Определенные таким образом типы могут использоваться с подмножеством обычных операций C. В настоящее время GCC разрешает использование следующих операторов с этими типами: +, -, *, /, unary minus, ^, |, &, ~, %.

Операции ведут себя как операции C++ valarrays. Сложение определяется как сложение соответствующих элементов операндов. Например, в приведенном ниже коде каждый из 4 элементов в a складывается с соответствующими 4 элементами в b, а результирующий вектор хранится в c.

typedef int v4si __attribute__ ((vector_size (16)));

v4si a, b, c;

c = a + b;

Вычитание, умножение, деление и логические операции работают аналогичным образом. Аналогичным образом, результат использования унарных операторов минус или дополнения для векторного типа — вектор, элементы которого являются отрицательными или дополненными значениями соответствующих элементов в операнде.

Возможны применение операторов сдвига <<, >> для векторов целочисленного типа. Операция определяется следующим образом: {a0, a1, …, an} >> {b0, b1, …, bn} == {a0 >> b0, a1 >> b1, …, an >> bn}. Векторные операнды должны иметь одинаковое количество элементов.

Для удобства разрешено использование бинарной векторной операции, где один операнд является скаляром. В этом случае компилятор преобразует скалярный операнд в вектор, где каждый элемент — скаляр из операции. Преобразование происходит только в том случае, если скаляр можно безопасно преобразовать в тип элемента вектора. Рассмотрим следующий код.

typedef int v4si __attribute__ ((vector_size (16)));

v4si a, b, c;
long l;

a = b + 1;    /* a = b + {1,1,1,1}; */
a = 2 * b;    /* a = {2,2,2,2} * b; */

a = l + a;    /* Error, cannot convert long to int. */

Векторы могут индексироваться так, как если бы вектор был массивом с тем же количеством элементов и базовым типом. Доступ за пределы границ вызывает неопределенное поведение во время выполнения. Предупреждения о доступе за пределы границ для индексации векторов могут быть включены с помощью -Warray-bounds.

Поддерживаются векторные сравнения со стандартными операторами сравнения: ==, !=, <, <=, >, >=. Операнды сравнения могут быть векторными выражениями целочисленного или вещественного типов. Сравнение между векторами целочисленного типа и векторов вещественного типа не поддерживается. Результат сравнения — вектор той же ширины и количества элементов, что и операнды сравнения, с целочисленным типом элемента со знаком.

Векторы сравниваются по элементам, генерируя 0 при ложном сравнении и -1 (константа соответствующего типа, где все биты установлены), в противном случае. Рассмотрим следующий пример.

typedef int v4si __attribute__ ((vector_size (16)));

v4si a = {1,2,3,4};
v4si b = {3,2,1,4};
v4si c;

c = a >  b;     /* The result would be {0, 0,-1, 0}  */
c = a == b;     /* The result would be {0,-1, 0,-1}  */

В C++ доступен тернарный оператор ?:. a?b:c, где b и c — векторы одного типа, а a — целочисленный вектор с тем же количеством элементов того же размера, что и b и c, вычисляет все три аргумента и создает вектор {a[0]?b[0]:c[0], a[1]?b[1]:c[1], …}. Обратите внимание, что в отличие от OpenCL, a интерпретируется как a != 0, а не как a < 0. Как и в случае с бинарными операциями, этот синтаксис также поддерживается, когда один из b или c является скаляром, который затем преобразуется в вектор. Если оба b и c являются скалярами, и тип true?b:c имеет тот же размер, что и тип элемента a, то b и c преобразуются в векторный тип, элементы которого имеют этот тип и имеют такое же количество элементов, как a.

В C++ доступны логические операторы !, &&, || для векторов. !v эквивалентно v == 0, a && b эквивалентно a!=0 & b!=0, и a || b эквивалентно a!=0 | b!=0. При смешанных операциях между скаляром s и вектором v, s && v эквивалентно s?v!=0:0 (оценка происходит по принципу короткого замыкания), а v && s эквивалентно v!=0 & (s?-1:0).

Поддержка перестановки векторов с помощью функций __builtin_shuffle (vec, mask) и __builtin_shuffle (vec0, vec1, mask). Обе функции строят перестановку элементов из одного или двух векторов и возвращают вектор того же типа, что и входной вектор(ы). mask — это целочисленный вектор той же ширины (W) и количества элементов (N), что и выходной вектор.

Элементы входных векторов нумеруются в порядке памяти vec0 с начала с 0 и vec1 с начала с N. Элементы mask рассматриваются по модулю N в случае с одним операндом и по модулю 2*N в случае с двумя операндами.

Рассмотрим следующий пример,

typedef int v4si __attribute__ ((vector_size (16)));

v4si a = {1,2,3,4};
v4si b = {5,6,7,8};
v4si mask1 = {0,1,1,3};
v4si mask2 = {0,4,2,5};
v4si res;

res = __builtin_shuffle (a, mask1);       /* res is {1,2,2,4}  */
res = __builtin_shuffle (a, b, mask2);    /* res is {1,5,3,6}  */

Обратите внимание, что __builtin_shuffle намеренно семантически совместим с функциями OpenCL shuffle и shuffle2.

Вы можете объявлять переменные и использовать их в вызовах и возвратах функций, а также в присваиваниях и некоторых преобразованиях. Вы можете указать векторный тип как тип возвращаемого значения функции. Векторные типы также могут использоваться как аргументы функций. Можно производить преобразования из одного векторного типа в другой, при условии, что они имеют одинаковый размер (фактически, вы также можете преобразовывать векторы в другие типы данных того же размера).

Нельзя выполнять операции между векторами разной длины или разной знаковостью без преобразования.

Преобразование векторов доступно с помощью функции __builtin_convertvector (vec, vectype) . vec должно быть выражением с целочисленным или плавающим векторным типом, а vectype — целочисленным или плавающим векторным типом с тем же количеством элементов. Результат имеет тип vectype и значение C-преобразования каждого элемента vec в тип элемента vectype.

Рассмотрим следующий пример,

typedef int v4si __attribute__ ((vector_size (16)));
typedef float v4sf __attribute__ ((vector_size (16)));
typedef double v4df __attribute__ ((vector_size (32)));
typedef unsigned long long v4di __attribute__ ((vector_size (32)));

v4si a = {1,-2,3,-4};
v4sf b = {1.5f,-2.5f,3.f,7.f};
v4di c = {1ULL,5ULL,0ULL,10ULL};
v4sf d = __builtin_convertvector (a, v4sf); /* d is {1.f,-2.f,3.f,-4.f} */
/* Equivalent of:
   v4sf d = { (float)a[0], (float)a[1], (float)a[2], (float)a[3] }; */
v4df e = __builtin_convertvector (a, v4df); /* e is {1.,-2.,3.,-4.} */
v4df f = __builtin_convertvector (b, v4df); /* f is {1.5,-2.5,3.,7.} */
v4si g = __builtin_convertvector (f, v4si); /* g is {1,-2,3,7} */
v4si h = __builtin_convertvector (c, v4si); /* h is {1,5,0,10} */

Иногда желательно писать код, используя смесь общих векторных операций (для ясности) и машиноспецифичных векторных встроенных функций (для доступа к векторным инструкциям, которые не представлены через общие встроенные функции). На x86 встроенные функции для целочисленных векторов обычно используют тот же векторный тип __m128i независимо от того, как они интерпретируют вектор, что делает необходимым преобразовывать их аргументы и возвращаемые значения из/в другие векторные типы. В C вы можете использовать тип union:

#include <immintrin.h>

typedef unsigned char u8x16 __attribute__ ((vector_size (16)));
typedef unsigned int  u32x4 __attribute__ ((vector_size (16)));

typedef union {
        __m128i mm;
        u8x16   u8;
        u32x4   u32;
} v128;

для переменных, которые могут использоваться как с встроенными операторами, так и с x86-встроенными функциями:

v128 x, y = { 0 };
memcpy (&x, ptr, sizeof x);
y.u8  += 0x80;
x.mm  = _mm_adds_epu8 (x.mm, y.mm);
x.u32 &= 0xffffff;

/* Instead of a variable, a compound literal may be used to pass the
   return value of an intrinsic call to a function expecting the union: */
v128 foo (v128);
x = foo ((v128) {_mm_adds_epu8 (x.mm, y.mm)});

Далее: Поддержка offsetof, Предыдущее: Получение адреса возврата или фрейма функции, Наверх: Расширения для языков семейства C [Оглавление][Индекс]

© Free Software Foundation
Licensed under the GNU Free Documentation License, Version 1.3.
https://gcc.gnu.org/onlinedocs/gcc-11.4.0/gcc/Vector-Extensions.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API